2011-11-29 20 views
25

ReSharper ha suggerito di cambiare daChe cosa significa <in TFrom, out TTo> significa?

interface IModelMapper<TFrom, TTo> 
{ 
    TTo Map(TFrom input); 
} 

in

interface IModelMapper<in TFrom, out TTo> 

Così ho indagare un po 'e si è conclusa la lettura this article (che si trova attraverso un Wikipedia articolo) e un po' di Google.

Non sono ancora sicuro di cosa ciò implicherebbe per la mia domanda, quindi sono tentato di non accettare il suggerimento. Quali sarebbero i benefici che questo cambiamento introdurrebbe e non sto considerando ignorando il suggerimento?

Più esplicitamente, perché dovrei accettarlo?

risposta

48

linea di fondo: ReSharper ha indagato il tipo, e ha scoperto che TFrom possono essere utilizzati contravariantly e TTo covariantly. Accettare il refactory ti permetterebbe di usare questi tipi con maggiore flessibilità, come descritto di seguito. Se ciò potrebbe essere di valore per te, accettalo.

Si noti, tuttavia, che l'accettazione di questo refactor comporterebbe l'obbligo di restrizioni sul modo in cui si utilizzano questi tipi in futuro. Se si scrive un metodo che prende come parametro TTo, si otterrà un errore del compilatore, poiché i tipi di coviariant non possono essere letti. E idem per TFrom: non si sarà mai in grado di avere un metodo che restituisce questo tipo, o ha un parametro out di questo tipo.


Che si sta dicendo che TFrom è controvariante, e che TTo è covariante. Queste caratteristiche sono state recentemente added to C#

Tipo covarianza significa che un più tipo specifico può essere passato, mentre controvarianza significa che un meno tipo specifico può essere passato.

IEnumerable<T> è un buon esempio di tipo covarianza. Dal momento che gli elementi di un IEnumerable<T> sono letti solo, è possibile impostare a qualcosa di più specifico:

IEnumerable<object> objects = new List<string>(); 

Pensate a cosa potrebbe accadere se (ipoteticamente) che si era permesso di fare questo per le collezioni che sono state di lettura/scrittura :

List<object> objects = new List<string>(); 
objects.Add(new Car()); 
//runtime exception 

essere di tipo covariante, un parametro generico deve essere utilizzato in una sola lettura modo strettamente ; deve essere sempre scritto fuori dal tipo e non leggere mai in (quindi le parole chiave). Ecco perché l'esempio IEnumerable<T> funziona, ma l'esempio List<T> no. A proposito, le matrici do supportano la covarianza del tipo (poiché Java lo fa, credo), e quindi questo stesso tipo di errore di runtime è possibile con gli array.

La contravarianza di tipo indica il contrario.Per supportare la contravarianza del tipo, è necessario leggere un parametro generico solo in e non scrivere mai in uscita. Questo consente di sostituire i tipi meno specifici in

Action<T> è un esempio di tipo contravaince:.

Action<object> objAction = (o => Console.WriteLine(o.ToString())); 
Action<string> strAction = objAction; 
strAction("Hello"); 

strAction è dichiarato di prendere un parametro di stringa, ma funziona bene se si sostituisce un tipo di oggetto. Una stringa verrà passata, ma se il delegato con cui lavora lavora sceglie di trattarlo come un oggetto, allora così sia. Nessun danno fatto.

Per completezza, Func<T> è il caso inverso di Action<T>; qui T viene restituito solo, quindi è covariante:

Func<string> strDelegate = () => "Hello"; 
Func<object> myObjFunc = strDelegate; 
object O = myObjFunc(); 

myObjectFunc è codificato per restituire un oggetto. Se lo si imposta su qualcosa che restituisce una stringa, quindi, di nuovo, nessun danno fatto.

+0

grazie, è davvero utile. – mhttk

+0

@mhttk: fantastico. Felice di aiutare –

+0

Probabilmente la migliore spiegazione di co/controvarianza che ho letto - grazie! –

2

Come esempio di come questa scelta potrebbe influenzare la vostra applicazione, suppone che si abbia un tipo CustomerAddressMapper che implementa IModelMapper<Customer, Address[]> e un altro SupplierAddressMapper che implementa IModelMapper<Supplier, Address[]>. I tipi Customer e Supplier condividono una classe base Company, ma la loro logica di indirizzo è distinta, quindi abbiamo bisogno di tipi separati per gestirlo.

Si supponga ora di disporre di un metodo che prende uno IMapper<Company, Address[]>. Prima della contravarianza dell'interfaccia, non sarebbe possibile passare le istanze di CustomerAddressMapper o SupplierAddressMapper a questo metodo. Ora, se si utilizza il modificatore in sul parametro di tipo TFrom, è possibile.

Inoltre, a causa della covarianza sul parametro TTo, si può anche passare un CustomerAddressMapper ai metodi che richiedono un IMapper<Customer, object>.