c# Универсальный перегруженный метод диспетчеризации неоднозначен

Я только что столкнулся с ситуацией, когда диспетчеризация метода была неоднозначной, и мне было интересно, может ли кто-нибудь объяснить, на каком основании компилятор (.NET 4.0.30319) выбирает, какую перегрузку вызывать.

interface IfaceA
{

}

interface IfaceB<T>
{
    void Add(IfaceA a);
    T Add(T t);
}

class ConcreteA : IfaceA
{

}

class abstract BaseClassB<T> : IfaceB<T>
{
    public virtual T Add(T t) { ... }
    public virtual void Add(IfaceA a) { ... }
}

class ConcreteB : BaseClassB<IfaceA>
{
    // does not override one of the relevant methods
}

void code()  
{
    var concreteB = new ConcreteB();

    // it will call void Add(IfaceA a)
    concreteB.Add(new ConcreteA());
}

В любом случае, почему компилятор меня не предупреждает или даже почему компилирует? Большое спасибо за любые ответы.


person Sebastian    schedule 18.05.2010    source источник
comment
Что произойдет, если вы ожидаете от вызова возвращаемого значения, например var result = concreteB.Add(new ConcreteA());?   -  person Tomas Aschan    schedule 18.05.2010
comment
Потому что ваш универсальный метод имеет тип возврата T, а другой имеет тип void. Компилятор может различать их.   -  person KroaX    schedule 18.05.2010
comment
компилятор будет жаловаться, что var нельзя использовать, потому что он не может присвоить void неявно типизированной переменной   -  person Sebastian    schedule 18.05.2010


Ответы (3)


Он следует правилам раздела 7.5.3.2 Спецификация C# 4 ("Улучшенный функциональный член").

Во-первых (ну, после того, как мы убедились, что оба метода применимы) нам нужно проверить преобразования типов аргументов в типы параметров. В данном случае это достаточно просто, потому что есть только один аргумент. Ни одно из преобразований типа аргумента в тип параметра не является «лучшим», поскольку оба преобразуют ConcreteA в IfaceA. Поэтому он переходит к следующему набору критериев, в том числе:

В противном случае, если MP имеет более конкретные типы параметров, чем MQ, то MP лучше, чем MQ. Пусть {R1, R2, …, RN} и {S1, S2, …, SN} представляют неконкретизированные и нерасширенные типы параметров MP и MQ. Типы параметров MP более специфичны, чем MQ, если для каждого параметра RX не менее специфичен, чем SX, и хотя бы для одного параметра RX более специфичен, чем SX: специфичен, чем SX:

  • Параметр типа менее специфичен, чем параметр, не являющийся типом.
  • ...

Таким образом, несмотря на то, что преобразование одинаково хорошо, перегрузка с использованием IfaceA напрямую (а не через делегатов) считается "лучше", поскольку параметр типа IfaceA является более конкретным, чем параметр типа T.

Невозможно заставить компилятор предупредить об этом поведении - это просто нормальное разрешение перегрузки.

person Jon Skeet    schedule 18.05.2010
comment
Хорошо спасибо миллион. Думаю, это означает, что я прочитаю эту спецификацию. Я просто перешел на Java и должен использовать C # для этого проекта. Хотя на самом деле я думаю, что это более полезно, но любая функция, которую я чувствую, добавляет еще несколько крайних случаев ... - person Sebastian; 18.05.2010

Потому что компилятор сначала выбирает самое конкретное.

Что произойдет, если вы позвоните так:

void code()   
{ 
    var concreteB = new ConcreteB(); 

    IfaceA  x = concreteB.Add(new ConcreteA()); 
} 
person Mitch Wheat    schedule 18.05.2010
comment
Но зачем void Add(IfaceA a) быть более конкретным? Это потому, что я не использую возвращенный результат? И кроме того, знаете ли вы, есть ли у компилятора возможность выдать мне предупреждение? - person Sebastian; 18.05.2010
comment
@sebgod: вы можете использовать директиву #warning для выдачи предупреждения компилятора. - person Tomas Aschan; 18.05.2010
comment
Код не будет компилироваться, если я выберу IfaceA x = concreteB.Add(new ConcreteA());: не может неявно конвертировать между IFaceA и void - person Sebastian; 18.05.2010
comment
похоже, что компилятор рассматривает только метод, основанный на интерфейсе, и не проверяет общую перегрузку, кстати, что означает один для Skeet? - person Sebastian; 18.05.2010
comment
Как вы видите, это мой первый вопрос на SO, мне грустно, что я не могу проголосовать за ваш ответ, говорит, что мне нужно 15 баллов, я думаю, что сегодня утром кофе был плохим ^^ - person Sebastian; 18.05.2010
comment
@Mitch: я был занят, печатая это :) - person Jon Skeet; 18.05.2010
comment
@ Томас, с предупреждением я имею в виду, что компилятор сообщит мне об этом, а не я хочу выдать предупреждение (все это было молчаливым взломом кода) - person Sebastian; 18.05.2010

Это чем-то напоминает мне «вывод типов вперед-вперед» в Jon Skeet's BrainTeaser. Если вы не хотите доверять компилятору, вы можете заставить его сделать выбор, вызвав Add<ConcreteA>(new ConcreteA())

person Sylvestre Equy    schedule 18.05.2010
comment
Это то, что мой учитель всегда говорил мне, никогда не доверяй своему компилятору - person Sebastian; 18.05.2010