2013-10-20 13 views
6

ho letto il seguente post sulla varianza contra e la risposta di Lasse V. Karlsen:Cos'è un uso comune di programmazione della contro-varianza?

Understanding Covariant and Contravariant interfaces in C#

Anche se capisco il concetto, non capisco perché è utile. Per esempio, perché qualcuno fare una lettura unica lista (come nel post: List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal>).

So anche che l'override parametri metodi può essere controindicato varianza (concettualmente Questo non è in uso in C#, Java e C++ per quanto ne so). Quali esempi ci sono in questo ha senso?

apprezzerei alcuni semplici esempi reali.

+1

Un'osservazione forse infiammatoria, ma si potrebbe sostenere che non sono utili. F # per esempio abbastanza deliberatamente non supporta né covarianza né controvarianza. –

+4

@DavidArno Ma nel contesto di C#, senza contro- o co-varianza, non ci sarebbe Linq. –

+0

@MatthewWatson, davvero? Bene, vivo e imparo! Non ero a conoscenza che linq li usasse :) –

risposta

2

(credo questa domanda è più su di covarianza, piuttosto che controvarianza, dal momento che l'esempio citato è a che fare con covarianza.)

List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal>

fuori contesto, questo è un po 'fuorviante. Nell'esempio citato, l'autore intendeva trasmettere l'idea che tecnicamente, se lo List<Fish> in realtà faceva riferimento a List<Animal>, lo corrispondesse a in modo sicuro aggiungendo uno Fish.

Ma, naturalmente, che sarebbe anche permette di aggiungere un Cow ad essa - che è chiaramente sbagliata.

Pertanto il compilatore non consente a di assegnare un riferimento List<Animal> a un riferimento List<Fish>.

Così, quando sarebbe questo in realtà al sicuro - e utile?

Si tratta di un incarico al sicuro se la raccolta non può essere modificato. In C#, un IEnumerable<T> può rappresentare una raccolta non modificabile.

Così si può fare questo in modo sicuro:

IEnumerable<Animal> animals = GetAccessToFishes(); // for some reason, returns List<Animal>

perché non c'è possibilità di aggiungere un non-Fish a animals. Non ha metodi per permetterti di farlo.

Così, quando questo sarebbe utile?

Questo è utile quando si desidera accedere ad alcuni metodo comune o di proprietà di una collezione che può contenere elementi di uno o più tipi derivati ​​da una classe base.

Ad esempio, si potrebbe avere una gerarchia che rappresentano le diverse categorie di azioni per un supermercato.

Diciamo che la classe base, StockItem, ha una proprietà double SalePrice.

Diciamo anche che hai un metodo, Shopper.Basket() che restituisce un IEnumerable<StockItem> che rappresenta gli articoli che un acquirente ha nel loro carrello. Le voci nel paniere possono essere di qualsiasi tipo di cemento derivato da StockItem.

In questo caso è possibile aggiungere i prezzi di tutti gli articoli nel carrello (ho scritto questo longhand senza l'utilizzo di Linq per rendere chiaro che cosa sta accadendo codice reale userebbe IEnumerable.Sum() naturalmente.):

IEnumerable<StockItem> itemsInBasket = shopper.Basket; 

double totalCost = 0.0; 

foreach (var item in itemsInBasket) 
    totalCost += item.SalePrice; 

controvarianza

un esempio di uso controvarianza è quando si desidera applicare qualche azione a un elemento o una collezione di oggetti attraverso un tipo di classe di base, anche se si dispone di un tipo derivato.

Ad esempio, si potrebbe avere un metodo che applica un'azione per ogni elemento di una sequenza di StockItem in questo modo:

void ApplyToStockItems(IEnumerable<StockItem> items, Action<StockItem> action) 
{ 
    foreach (var item in items) 
     action(item); 
} 

Utilizzando l'esempio StockItem, supponiamo che ha un metodo Print() che si può usare per stamparlo su una ricevuta di cassa. Si potrebbe chiamare poi usarlo in questo modo:

Action<StockItem> printItem = item => { item.Print(); } 
ApplyToStockItems(shopper.Basket, printItem); 

In questo esempio, i tipi di voci nel paniere potrebbe essere Fruit, Electronics, Clothing e così via. Ma poiché derivano tutti da StockItem, il codice funziona con tutti loro.

Speriamo che l'utilità di questo tipo di codice sia chiara! Questo è molto simile al modo in cui funzionano molti dei metodi di Linq.

1

Covariance è utile per i repository di sola lettura (fuori); contravarianza per i repository di sola scrittura (in).

public interface IReadRepository<out TVehicle> 
{ 
    TVehicle GetItem(Guid id); 
} 

public interface IWriteRepository<in TVehicle> 
{ 
    void AddItem(TVehicle vehicle); 
} 

In questo modo, un esempio di IReadRepository<Car> è anche un'istanza di IReadRepository<Vehicle> perché se si ottiene una macchina fuori dal repository, ma è anche un veicolo; tuttavia, un'istanza di IWriteRepository<Vehicle> è anche un'istanza di IWriteRepository<Car>, perché se è possibile aggiungere un veicolo al repository, è quindi possibile scrivere un'automobile nel repository.

Questo spiega il ragionamento alla base delle parole chiave out e in per covarianza e controvarianza.

Per quanto riguarda il motivo per cui si potrebbe desiderare di effettuare questa separazione (utilizzando interfacce separate di sola lettura e di sola scrittura, che verrebbero probabilmente implementate dalla stessa classe concreta), ciò consente di mantenere una netta separazione tra i comandi (scrivere operazioni) e query (operazioni di lettura), che probabilmente avranno requisiti diversi in termini di prestazioni, coerenza e disponibilità e pertanto necessitano di strategie diverse nella base di codice.

0

Se non si coglie veramente il concetto di contravarianza, allora problemi come questo possono verificarsi (e si verificheranno).

Costruiamo l'esempio più semplice:

public class Human 
{ 
    virtual public void DisplayLanguage() { Console.WriteLine("I do speak a language"); } 
} 
public class Asian : Human 
{ 
    override public void DisplayLanguage() { Console.WriteLine("I speak chinesse"); } 
} 

public class European : Human 
{ 
    override public void DisplayLanguage() { Console.WriteLine("I speak romanian"); } 
} 

Ok ecco un esempio della varianza contra

public class test 
{ 
    static void ContraMethod(Human h) 
    { 
     h.DisplayLanguage(); 
    } 

    static void ContraForInterfaces(IEnumerable<Human> humans) 
    { 
     foreach (European euro in humans) 
     { 
      euro.DisplayLanguage(); 
     } 
    } 
    static void Main() 
    { 
     European euro = new European(); 
     test.ContraMethod(euro); 
     List<European> euroList = new List<European>() { new European(), new European(), new European() }; 

     test.ContraForInterfaces(euroList); 
    } 
} 

Prima di .Net 4.0 i ContraForInterfaces metodo non è stato permesso (in modo che effettivamente Corretto un bug :)).

Ok, ora qual è il significato del metodo ContraForInterfaces? È semplice, una volta creato un elenco di europei, gli oggetti all'interno sono SEMPRE europei, anche se li si passa a un metodo che prende IEnumerable <>. In questo modo chiamerai sempre il metodo giusto per il tuo oggetto. Covarianza e ContraVariance è solo polimorfismo parametro (e nient'altro) applicato ora anche a delegati e interfacce. :)

Problemi correlati