О разрешении перегрузки оператора

Предположим, есть два класса со следующим неявным и явным шаблоном оператора:

class Foo
{
    public static implicit operator decimal (Foo foo)
    {
        throw new NotImplementedException();
    }

    public static implicit operator Foo (decimal value)
    {
        throw new NotImplementedException();
    }

    public static Foo operator +(Foo left, Foo right)
    {
        throw new NotImplementedException();
    }
}

class Bar
{
    public static explicit operator decimal (Bar bar)
    {
        throw new NotImplementedException();
    }

    public static explicit operator Foo(Bar bar)
    {
        throw new NotImplementedException();
    }
}

Теперь рассмотрим следующий код:

var foo = new Foo();
var bar = new Bar();
var resultFooAddBar = foo + (decimal)bar;

Неявно введенный resutlFooAddBar разрешается в Foo, а оператор добавления разрешается в Foo Foo.operator +. Почему этот код не выдает неоднозначную ошибку? Оператор мог бы также разрешить decimal decimal.operator +. Это потому, что определяемые пользователем операторы всегда считаются более подходящими? Тем не менее, выбор кажется немного странным, учитывая, что Bar имеет явное приведение к Foo, которое не использовалось, что явно определяет, какой оператор хотел бы использовать программист:

var resultFooAddBar = foo + (Foo)bar; //Хорошо, я прямо говорю, что хочу Foo Foo.operator +

Если вместо decimal мы используем третий класс Tango, определяющий Tango Tango.operator + (Tango, Tango), и те же неявные и явные шаблоны операторов, то компилятор выдает ошибку неоднозначного вызова.

Почему такое различие между операторами, определяемыми пользователем, и операторами, не определяемыми пользователем?

ОБНОВЛЕНИЕ: я создал отдельную сборку, включающую следующий класс, чтобы проверить объяснение Servy:

namespace ExternalAssembly
{
    public class Tango
    {
        public static Tango operator +(Tango left, Tango right)
        {
            throw new NotImplementedException();
        }
    }
}

А затем изменил decimal на Tango в Foo и Bar и добавил необходимую ссылку на dll ExternalAssembly. В этом случае я все еще получаю, что оператор '+' неоднозначен для операндов 'ConsoleApplication.Foo' и 'ExternalAssembly.Tango'. Почему бы компилятору в этом случае не выбрать ту же перегрузку Foo Foo.operator +, что и в моем первоначальном вопросе с decimal?


person InBetween    schedule 18.09.2014    source источник
comment
@Servy decimal + decimal. Существует неявное преобразование из Foo в decimal, но вместо этого компилятор выбирает неявное преобразование из decimal в Foo. См. последний абзац (только что отредактированный).   -  person InBetween    schedule 18.09.2014


Ответы (1)


Алгоритмы разрешения перегрузок имеют ряд показателей «лучшести», по которым они определяют, какие из нескольких применимых перегрузок метода/оператора следует использовать. Только в том случае, если ни одна из этих метрик не имеет окончательно «лучшей» перегрузки, отображается ошибка неоднозначности.

Одним из показателей качества является «близость» определения рассматриваемой перегрузки к месту вызова. Определение в том же классе «ближе», чем определение вне его, определение во внешнем классе ближе, чем определения вне этого родительского типа, определения в том же пространстве имен ближе, чем определения во внешних пространствах имен и т. д. Ваше определение "ближе", чем оператор десятичной дроби +. (Дополнительную информацию по этому вопросу см. в этой статье.)

person Servy    schedule 18.09.2014
comment
Хорошо, это то, чего я более или менее ожидал, видя, как ведет себя разрешение оператора. Но все же мне кажется, что как и во всех конструкциях, есть компромиссы, чтобы не усложнять чрезмерно. В этом случае наличие явного приведения, которое вы решили не использовать, должно быть своего рода флагом для компилятора, чтобы понять, что, возможно, он выбирает не лучший оператор. Как бы то ни было, в моей стране говорят: совершенство — враг добра, поэтому то, как это работает, вероятно, самое лучшее. - person InBetween; 18.09.2014
comment
Если я создаю, скажем, класс Tango в отдельной сборке и с другим пространством имен, я все равно получаю неоднозначный вызов. Почему в таком случае оно должно отличаться от decimal? - person InBetween; 19.09.2014