2013-09-03 13 views
6

ich einige Erweiterungsmethoden, die eine Expression Parameter verwenden, in einer Eigenschaft Mitglied und wirken auf sie zu ziehen, und ich habe eine Überlastung für den speziellen Fall, in dem das Element ein IEnumerable <>. Es scheint jedoch nicht die erwartete Methodenüberladung zu entsprechen, wenn sie innerhalb einer generischen Klasse aufgerufen wird (für r4 unter). Außerhalb der Klasse wird die richtige Methode ausgewählt.Erweiterung Methodenauswahl unter Verwendung von generischen Typen und Ausdrücke

Was geht hier vor? Wird das jemals funktionieren oder muss ich einen neuen Ansatz finden?

(Dies ist in C# 5)

public class Test 
{ 
    public void MyTest() 
    { 
     // returns "Object" 
     var r1 = new MyClass<object>().Ext(a => a.Content); 

     // returns "Enumerable" 
     var r2 = new MyClass<IEnumerable<object>>().Ext(a => a.Content); 

     // returns "Object" 
     var r3 = new MyClass<object>().TestExt(); 

     // returns "Object" (I was expecting "Enumerable") 
     var r4 = new MyClass<IEnumerable<object>>().TestExt(); 

     // returns "Enumerable" 
     var r5 = new MyClass<int>().TestExt2(); 
    } 
} 

public class MyClass<T> 
{ 
    public T Content { get; set; } 

    public IEnumerable<object> OtherContent { get; set; } 

    public string TestExt() 
    { 
     return this.Ext(a => a.Content); 
    } 

    public string TestExt2() 
    { 
     return this.Ext(a => a.OtherContent); 
    } 
} 

public static class MyExtensions 
{ 
    public static string Ext<T>(this T obj, Expression<Func<T, IEnumerable<object>>> memberExpression) 
    { 
     return "Enumerable"; 
    } 

    public static string Ext<T>(this T obj, Expression<Func<T, object>> memberExpression) 
    { 
     return "Object"; 
    } 
} 
+0

Was ist hier falsch? –

+0

@KingKing Siehe den Kommentar neben der Erklärung von 'r4' –

Antwort

4

Generika sind nicht dynamische Typisierung. Welche Überladung zu nennen ist, wird zur Kompilierzeit eingefroren. Wenn das Programm zu einem späteren Zeitpunkt ausgeführt wird, spielt es keine Rolle, ob die Variable einen spezifischeren Laufzeittyp enthält, da die Überladung zur Kompilierungszeit behoben wurde.

Ihre Methode:

public string TestExt() 
{ 
    return this.Ext(a => a.Content); 
} 

hat zur Compile-Zeit auf eine bestimmte Überlastung von Ext zu binden. Da alles, was wir über T (der Typ von a.Content) in der Klasse MyClass<T> wissen, ist, dass es in object umwandelbar ist, gibt es wirklich nur eine Überladung zur Auswahl, so dass dies für den Compiler einfach ist.

Ab diesem Zeitpunkt ist der Methodenkörper TestExt fest programmiert, um die spezifische Überlast von Ext aufzurufen.


EDIT: Hier ist ein viel einfacheres Beispiel:

static void Main() 
{ 
    IEnumerable<object> e = new List<object>(); 
    var r = Generic(e); 
} 

static string Generic<T>(T x) 
{ 
    return Overloaded(x); 
} 

static string Overloaded(IEnumerable<object> x) 
{ 
    return "Enumerable"; 
} 
static string Overloaded(object x) 
{ 
    return "Object"; 
} 

und wie Sie jetzt verstehen, r wird "Object".

(Wenn Sie T irgendwie eingeschränkt haben, zum Beispiel where T : IEnumerable<object>, würde die Dinge anders sein).

Auch das ist das gleiche für Betreiber. Zum Beispiel ist der Operator == in dem Sinne überladen, dass er in einer Richtung für allgemeine Referenztypen und in einer anderen für Strings funktioniert. So im Beispiel unten, Betreiber == übernimmt die Rolle von Overloaded aus der Zeit vor:

static void Main() 
{ 
    string str1 = "abc"; 
    string str2 = "a"; 
    str2 += "bc";  // makes sure this is a new instance of "abc" 

    bool b1 = str1 == str2;  // true 
    bool b2 = Generic(str1, str2); // false 
} 

static bool Generic<T>(T x, T y) where T : class 
{ 
    return x == y; 
} 

wo b2false wird.