2011-01-05 7 views
6

Possible Duplicate:
Why is Func<T> ambiguous with Func<IEnumerable<T>>?Generics, Überladungsauflösung und Delegierten

ich (sorry, kann keinen besseren Titel finden) eine sehr seltsame Überladungsauflösung Problem mit Generika bemerkt ...

Betrachten Sie die folgenden Methoden:

static void Foo<TSource>(TSource element, Func<TSource, int> selector) 
{ 
    "int".Dump(); 
} 

static void Foo<TSource>(TSource element, Func<TSource, double> selector) 
{ 
    "double".Dump(); 
} 

static T Identity<T>(T value) 
{ 
    return value; 
} 

(C# 4, getestet in LINQPad)

Wenn ich versuche, Foo mit einem Lambda-Ausdruck als Wähler zu nennen , Funktioniert alles einwandfrei:

Foo(42, x => x); // prints "int" 

Aber wenn ich x => x mit Identity ersetzen, kann der Compiler nicht zwischen den 2 Foo Überlastungen entscheiden:

Foo(42, Identity); 
// The call is ambiguous between the following methods or properties: 
// 'UserQuery.Foo<int>(int, System.Func<int,int>)' and 
// 'UserQuery.Foo<int>(int, System.Func<int,double>)' 

Wie kann die zweite Überlastung sein ein gültiger Kandidat? Typinferenz bestimmt richtig, dass TSource ist int, so dass die T Parameter für die Identity Methode hat int auch sein, so dass der Rückgabetyp hat int zu sein ... Identity ein Func<int,int> oder ein Func<double,double> sein könnte, aber nicht ein Func<int,double>!

Und es wird schlimmer! Selbst wenn ich alle Typparameter explizit spezifiziere, erhalte ich immer noch den gleichen Fehler:

Foo<int>(42, Identity<int>); // The call is ambiguous... 

Wie kann es hier eine Mehrdeutigkeit geben? Soweit ich das beurteilen kann, gibt es keine Möglichkeit, die Überlastung, die ein Func<int,double> nimmt, ein Kandidat sein kann. Ich denke, die Erklärung muss irgendwo in den Spezifikationen sein, aber ich kann das relevante Bit nicht finden ... oder es könnte ein Fehler im Compiler sein, aber ich denke, es ist unwahrscheinlich.

Beachten Sie, dass es funktioniert, wenn ich den Delegaten explizit erstellen:

Foo(42, new Func<int, int>(Identity)); // prints "int" 

So könnte jemand erklären, was hier vor sich geht? Warum funktioniert es auch mit einem Lambda, aber nicht mit einer Methodengruppe?

+4

Geduldig warten auf Eric Lippert * die * Antwort zu posten. –

+0

Was passiert unter C# 3? Ich vermute, dass dies etwas mit Typvarianz in Generika zu tun haben könnte. –

+0

@Anon, ich habe nicht mit C# 3 versucht, aber ich glaube nicht, dass es etwas mit Varianz zu tun hat, da die Varianz nicht auf Werttypen –

Antwort

3

Ist es nicht einfach weil der Rückgabetyp nicht Teil der Signatur der Methode ist?

Die Tatsache, dass die Argumentart und der Rückgabetyp der Methode Identity<T> garantiert identisch sind, wird vom Compiler nicht berücksichtigt, wenn versucht wird, zu entscheiden, welche Überladung von Foo<TSource> erforderlich ist. Wenn der Rückgabetyp nicht berücksichtigt wird, kann Identity<int> ebenfalls in Func<int, int>, Func<int, double> oder Func<int, anything> umgewandelt werden.

+0

Ich denke, das ist es. Mal sehen, was Eric dazu sagen wird! – Lucero

+0

Guter Punkt, ich habe vergessen, dass der Rückgabetyp nicht Teil der Signatur war ... –

1

Ich denke, dass LukeH richtig ist. Um jedoch das zweite Bit Ihrer Frage zu beantworten: Der Delegierte des Lambda wird bereits alle Typen ausgefüllt haben (z. B. immer eine Func<int, int>, wenn TSource eine int ist), weshalb es in diesem Fall keine Mehrdeutigkeit gibt. Es ist nicht wie eine Funktionssignatur, bei der der Rückgabetyp ignoriert wird.

+0

Ja, ich denke das ist die richtige Erklärung ... –

+1

Absolut nicht. Lambdas sind sehr kontextsensitiv. Tatsächlich ist die Lambda-Version mehrdeutig. http://ideone.com/IpUmb –

+0

@Ben Voigt, ich verstehe nicht, was du meinst ... was soll dieser Code illustrieren? Es funktioniert gut mit einem Lambda, also muss es eindeutig sein ... –