(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.
Un'osservazione forse infiammatoria, ma si potrebbe sostenere che non sono utili. F # per esempio abbastanza deliberatamente non supporta né covarianza né controvarianza. –
@DavidArno Ma nel contesto di C#, senza contro- o co-varianza, non ci sarebbe Linq. –
@MatthewWatson, davvero? Bene, vivo e imparo! Non ero a conoscenza che linq li usasse :) –