2012-05-11 9 views
16

Questa è una domanda un po 'lunga, quindi per favore abbiate pazienza con me.Un elenco <> di Func <> s, errore di compilazione con tipo di ritorno generico, ma perché?

Devo creare una mappatura tra un insieme di stringhe e le corrispondenti chiamate di metodo generiche per ogni stringa. Comunque ho incontrato un problema di compilazione, spiegato in basso.

Nel mio scenario sto usando un Dictionary<>, ma il problema esiste allo stesso modo per un List<>. Per semplicità sto usando un List<> nell'esempio qui sotto.

Considerare questi tre classi:

public abstract class MyBase { /* body omitted */ } 
public class MyDerived1 : MyBase { /* body omitted */ } 
public class MyDerived2 : MyBase { /* body omitted */ } 

e un metodo in qualche altra classe:

public class Test 
{ 
    public T GetT<T>() where T : MyBase { /* body omitted */ } 
} 

In un'altra classe, posso dichiarare una List<Func<MyBase>> come questo:

public class SomeClass 
{ 
    public void SomeFunc() 
    { 
     var test = new Test(); 

     var list1 = new List<Func<MyBase>> 
      { 
       test.GetT<MyDerived1>, 
       test.GetT<MyDerived2> 
      }; 
    } 
} 

Questo va tutto bene e bene.

Ma, cosa succede se voglio avere una funzione che restituisce un classe generica come questa:

public class RetVal<T> where T : MyBase { /* body omitted */ } 

public class Test 
{ 
    public RetVal<T> GetRetValT<T>() where T : MyBase 
    { 
     return null; 
    } 
} 

E voglio creare un equivalente List<> di utilizzare questa funzione. cioè una lista >>?

public class Class1 
{ 
    public void SomeFunc() 
    { 
     var test = new Test(); 

     var list2 = new List<Func<RetVal<MyBase>>> 
      { 
       test.GetRetValT<MyDerived1>, // compile error 
       test.GetRetValT<MyDerived2> // compile error 
      }; 
    } 
} 

Ottengo errori di compilazione di Expected a method with 'RetVal<MyBase> GetRetValT()' signature.

Quindi, c'è un modo per aggirare questo, o c'è un approccio alternativo che posso usare per creare la mia stringa ... metodo generico chiamate mappature?

+1

E 'una questione di varianza. Fondamentalmente, un 'RetVal ' * non è * a 'RetVal '. Maggiori dettagli quando ho tempo. –

+0

Ciò che @JonSkeet ha detto è vero. È una legge in C#. Se vuoi inserire 'RetVal ' MyDerived a 'RetVal ' MyBase ', devi fare in modo che il' RetVal' accetti un generico non tipizzato, ad esempio 'RetVal '. Vedere un esempio qui: http://www.ienablemuch.com/2012/05/generics-object-orientation-untyped.html –

+0

Posso essere del tutto fuori base su questo, ma non vorrei l'idea di restituire un generico violare l'idea che il generico debba (eventualmente) risolversi in qualcosa che il compilatore conosce al momento della compilazione? Cioè, la ragione per cui il compilatore lancia questo errore è che si aspetta che tu fornisca il metodo che può restituire un tipo specifico (che mi rendo conto è opposto a quello che stai cercando di fare). Solo un'osservazione, come ho detto, può essere molto lontana qui ... –

risposta

16

C# consente solo la covarianza sulle interfacce. Ciò significa che non puoi trasmettere automaticamente un RetVal<MyDerived1> a RetVal<MyBase>.Se RetVal dovrebbe essere covarianti, creare un'interfaccia per esso, in questo modo:

public interface IRetVal<out T> 
{ 

} 
public class RetVal<T> : IRetVal<T> where T : MyBase { /* body omitted */ } 

public class Test 
{ 
    public IRetVal<T> GetRetValT<T>() where T : MyBase 
    { 
     return null; 
    } 
} 

Poi questo codice funzionerà:

var list2 = new List<Func<IRetVal<MyBase>>> 
     { 
      test.GetRetValT<MyDerived1>, 
      test.GetRetValT<MyDerived2> 
     }; 
+0

se si utilizza 'dataSources = new List >>' in *** un altro AppDomain ***: 'AppDomain.Create' – Kiquenet

+0

@Kiquenet: Non capisco il tuo commento Puoi chiarire? – StriplingWarrior

2

Ho avuto un problema simile solo di recente.

C# non supporta la covarianza del tipo di ritorno ai fini dell'implementazione dell'interfaccia o del sovracampo del metodo virtuale. Vedi questa domanda per i dettagli:

Does C# support return type covariance?.

Si può essere in grado di incidere questo, mio ​​fare:

public RetVal<R> GetRetValT<T,R>() where T : MyBase where R : MyBase 
{ 
    return null; 
} 

//Then, change this to: 
test.GetRetValT<MyDerived1, MyBase> 
7

Il problema è il classico covarianza/controvarianza dei farmaci generici. Si presuppone che poiché MyDerived1 e MyDerived2 ereditano da MyBase, che uno RetVal<MyDerived1> erediti da RetVal<MyBase> e che non lo sia.

Il modo più semplice per risolvere questo problema è probabilmente quello di modificare il codice a:

var list2 = new List<Func<RetVal<MyBase>>> 
     { 
      () => (MyBase)test.GetRetValT<MyDerived1>, 
      () => (MyBase)test.GetRetValT<MyDerived2> 
     }; 

o meglio ancora, come sottolinea JS fuori nei commenti, basta cambiare RetVal<T> essere covariante se possibile:

public interface IRetVal<out T> { ... } 

public class RetVal<T> : IRetVal<T> { ... } 
+3

Non proprio - in C# 4, 'Func ' * è * covariante in T, motivo per cui il primo esempio funziona. È 'RetVal ' che non è covariante qui. –

+1

Destra, scusa ... Risolverà i problemi –

3

parametri di tipo generico per le classi non possono essere covarianti, ma possono essere covariante per interfacce. Puoi fare ciò che vuoi con un'interfaccia IRetVal<T> invece della classe RetVal<T>, se il parametro type è dichiarato covariante. Per dichiarare un parametro di tipo interfaccia come covariante, deve essere utilizzato solo nelle posizioni di "output".

Per illustrare, questo codice non compilerà:

interface IRetVal<out T> 
{ 
    T Value { get; } 
    void AcceptValue(T value); 
} 

per ottenere il codice da compilare, è necessario rimuovere il modificatore out dal parametro tipo, o rimuovere il metodo AcceptValue (perché usa T per un parametro: una posizione di input).

Con l'interfaccia IRetVal<out T>, si può fare questo:

public class MyBase { } 
public class MyDerived1 : MyBase { } 
public class MyDerived2 : MyBase { } 

public interface IRetVal<out T> where T : MyBase { /* body omitted */ } 

public class Test 
{ 
    public IRetVal<T> GetRetValT<T>() where T : MyBase 
    { 
     return null; 
    } 
} 

public class Class1 
{ 
    public void SomeFunc() 
    { 
     var test = new Test(); 

     var list = new List<Func<IRetVal<MyBase>>> 
     { 
      test.GetRetValT<MyDerived1>, 
      test.GetRetValT<MyDerived2> 
     }; 
    } 
} 
Problemi correlati