2013-09-03 10 views
6

ho alcuni metodi di estensione che utilizzano un parametro Expression per tirare in un elemento di struttura e agire su di esso, e ho un sovraccarico per il caso particolare in cui l'elemento è un IEnumerable <>. Tuttavia, non sembra corrispondere al sovraccarico del metodo previsto quando viene richiamato da una classe generica (per r4 al di sotto di). Al di fuori della classe viene selezionato il metodo corretto.estensione utilizzando tipi generici ed espressioni

Cosa sta succedendo qui? Funzionerà mai o devo trovare un nuovo approccio?

(questo è 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

Cosa c'è che non va qui? –

+0

@KingKing Vedi il commento accanto alla dichiarazione di 'r4' –

risposta

4

Generics non sono tipizzazione dinamica. Quale sovraccarico per chiamare è congelato in fase di compilazione. Quando il programma viene eseguito in un secondo momento, anche se la variabile contiene un tipo di esecuzione più specifico, non importa, perché il sovraccarico è stato risolto in fase di compilazione.

Il tuo metodo:

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

deve legarsi al momento della compilazione di un sovraccarico specifico di Ext. Poiché tutto ciò che sappiamo su T (il tipo di a.Content) nella classe MyClass<T> è che è convertibile in object, c'è davvero solo un sovraccarico tra cui scegliere, quindi questo è facile per il compilatore.

Da questo momento in poi, il corpo del metodo TestExt è hardcoded per chiamare lo specifico sovraccarico di Ext.


EDIT: Ecco un esempio molto più semplice:

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"; 
} 

e come si capisce, ormai, diventa r"Object".

(Se in qualche modo si vincola T, ad esempio where T : IEnumerable<object>, le cose sarebbero diverse).

Anche questo è lo stesso per gli operatori. Ad esempio l'operatore == è sovraccarico nel senso che funziona in un modo per i tipi di riferimento generali e in un altro per le stringhe. Così, nel seguente esempio, l'operatore == assume il ruolo di Overloaded da prima:

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; 
} 

dove b2 diventa false.