2016-07-25 2 views
4

У меня возникли проблемы с получением компилятора для устранения правильной перегрузки для метода расширения. Лучший способ объяснить мне небольшой код. Вот сценарий LINQPad, который демонстрирует проблему. Это не будет компилироваться из-за этой проблемы у меня:Устранение перегрузки по методу Weird расширения

void Main(){ 
    new Container<A>().Foo(a=>false); 
} 

interface IMarker{} 
class A : IMarker{ 
    public int AProp{get;set;} 
} 
class B : IMarker{ 
    public int BProp{get;set;} 
} 
class Container<T>{} 

static class Extensions{ 
    public static void Foo<T>(this T t, Func<T, bool> func) 
     where T : IMarker{ 
     string.Format("Foo({0}:IMarker)", typeof(T).Name).Dump(); 
    } 
    public static void Foo<T>(this Container<T> t, Func<T, bool> func){ 
     string.Format("Foo(Container<{0}>)", typeof(T).Name).Dump(); 
    } 
} 

Там ошибки я получаю:

Вызов неоднозначен между следующими методами или свойствами: «Extensions.Foo<Container<A>>(Container<A>, System.Func<Container<A>,bool>)» и 'Extensions.Foo<A>(Container<A>, System.Func<A,bool>) '

Мне кажется, что это не двусмысленно. Первый метод не принимает Container<T>, только IMarker. Похоже, что общие ограничения не помогает в разрешении перегрузки, но в этой версии кода, они кажутся:

void Main(){ 
    new A().Bar(); 
    new A().Foo(a=>a.AProp == 0); 
    new A().Foo(a=>false); // even this works 
    new A().Foo(a=>{ 
     var x = a.AProp + 1; 
     return false; 
    }); 

    new Container<A>().Bar(); 
    new Container<A>().Foo(a=>a.AProp == 0); 
    new Container<A>().Foo(a=>{ 
     var x = a.AProp + 1; 
     return false; 
    }); 
} 

interface IMarker{} 
class A : IMarker{ 
    public int AProp{get;set;} 
} 
class B : IMarker{ 
    public int BProp{get;set;} 
} 
class Container<T>{} 

static class Extensions{ 
    public static void Foo<T>(this T t, Func<T, bool> func) 
     where T : IMarker{ 
     string.Format("Foo({0}:IMarker)", typeof(T).Name).Dump(); 
    } 
    public static void Foo<T>(this Container<T> t, Func<T, bool> func){ 
     string.Format("Foo(Container<{0}>)", typeof(T).Name).Dump(); 
    } 

    public static void Bar<T>(this T t) where T : IMarker{ 
     string.Format("Bar({0}:IMarker)", typeof(T).Name).Dump(); 
    } 
    public static void Bar<T>(this Container<T> t){ 
     string.Format("Bar(Container<{0}>)", typeof(T).Name).Dump(); 
    } 
} 

Это компилирует и дает ожидаемые результаты:

Бар (А: IMarker)
Foo (А: IMarker)
Foo (А: IMarker)
Foo (А: IMarker)
Бар (Контейнер < >)
Foo (Container < >)
Foo (Container < >)

кажется, только есть проблема, когда я не ссылаться на параметр лямбда в лямбда-выражения, а затем только с Container<T> класс. При вызове Bar нет лямбды, и он отлично работает. При вызове Foo с возвращаемым значением, основанным на параметре лямбда, он работает нормально. Даже если возвращаемое значение лямбда совпадает с значением в примере, который не компилируется, но параметр лямбда ссылается на фиктивное назначение, он работает.

Почему это работает в этих случаях, но не в первом? Я что-то делаю неправильно, или я нашел ошибку компилятора? Я подтвердил поведение как на C# 4, так и на C# 6.

+0

@ManoDestra: Редактирует, потому что вам не нравится, где я размещаю свои фигурные скобки, не приветствуются. –

+0

Это C#. Следовательно, редактирование. – ManoDestra

+0

Я не вижу твоей точки. –

ответ

5

О, я получил это после повторного чтения собственного ответа! Nice question =) Перегрузка не работает, потому что она не принимает во внимание ограничение where T:IMaker при разрешении перегрузки (ограничение не является частью сигнатуры метода). При ссылке параметр в лямбда вы (можете) добавить подсказку компилятору:

  1. Это работает:

    new Container<A>().Foo(a => a.AProp == 0); 
    

    , потому что здесь мы намекают, что: с А;

  2. Это не работает, даже со ссылкой на параметр:

    new Container<A>().Foo(a => a != null); 
    

    , потому что до сих пор не достаточно информации, чтобы определить тип.

Насколько я понимаю, спецификацию, в «сценарии Foo» логический вывод может не на, таким образом, второй (Func) аргумент делает вызов неоднозначен.

Вот что Spec (25.6.4) говорит:

Определение типа происходит как часть обработки время компиляции вызова метода (§14.5.5.1) и происходит до шага разрешения перегрузки по вызов. Когда определенная группа методов указана в вызове метода, и никакие аргументы типа не указаны как часть вызова метода, вывод типа применяется к каждому обобщенному методу в группе методов. Если вывод типа преуспевает, то аргументы inferred type используются для определения типов аргументов для последующего разрешения перегрузки.

Если разрешение перегрузки выбирает общий метод в качестве вызывающего, то аргументы inferred type используются в качестве аргументов типа времени выполнения для вызова. Если вывод типа для конкретного метода не выполняется, этот метод не участвует в разрешении перегрузки. Сбой вывода типа сам по себе не вызывает ошибки времени компиляции. Тем не менее, это часто приводит к ошибке времени компиляции, когда разрешение перегрузки затем не находит каких-либо применимых методов.

Теперь позвольте перейти к довольно простому сценарию «Бар». После вывода типа мы получим только один метод, потому что только один применим:

  1. Bar(Container<A>) для new Container<A>() (не реализует IMaker)
  2. Bar(A) для new A() (не Container)

И вот ECMA-334 specification, на всякий случай. P.s. Я не уверен на 100%, что все правильно, но я предпочитаю думать, что я понял основную роль.

+0

Хорошо, я с вами до сих пор с ограничениями, не являющимися частью сигнатуры метода (и поэтому вы не можете перегружать метод только ограничением) и что использование параметра лямбда дает компилятору подсказку однозначно выберите перегрузку. Я подозревал это. Но то, чего я до сих пор не понимаю, - это то, почему звонки на «Бар» работают во втором фрагменте. Им совершенно не хватает лямбды. –

+0

Я думаю, наш ответ находится где-то в 25.6.4 и 14.4.2 спецификации языка. Но для меня 7 утра, поэтому я не смогу придумать какие-либо идеи до завтра. –

+0

Не хотите ли вы расширить это в своем ответе, возможно, с некоторыми цитатами из спецификации? –

1

Sergey выяснено, почему то, что я пытался сделать, не работает, я думаю. Это то, что я решил сделать вместо этого:

void Main(){ 
    new A().Bar(); 
    new A().Foo(a=>a.AProp == 0); 
    new A().Foo(a=>false); 

    new Container<A>().Bar(); 
    new Container<A>().Foo(a=>a.AProp == 0); 
    new Container<A>().Foo(a=>false); // yay, works now! 
} 

interface IMarker<T>{ 
    T Source{get;} 
} 

class A : IMarker<A>{ 
    public int AProp {get;set;} 
    public A Source{get{return this;}} 
} 
class B : IMarker<B>{ 
    public int BProp {get;set;} 
    public B Source{get{return this;}} 
} 

class Container<T> : IMarker<T>{ 
    public T Source{get;set;} 
} 

static class Extensions{ 
    public static void Foo<T>(this IMarker<T> t, Func<T, bool> func){} 
    public static void Bar<T>(this IMarker<T> t){} 
} 

К сожалению, для меня это большое изменение для моего приложения. Но, по крайней мере, уровень расширения будет проще, и, в конце концов, он будет менее двусмысленным как для компилятора, так и для людей, и это хорошо.

+0

Если контейнер действительно значимый IMaker, тогда он выглядит как хорошее решение по рефакторингу. –

Смежные вопросы