Quando dovrei o non dovrei usare vincoli di tipo generico?
-
10-07-2019 - |
Domanda
Ho una classe base:
public abstract class StuffBase
{
public abstract void DoSomething();
}
E due classi derivate
public class Stuff1 : StuffBase
{
public void DoSomething()
{
Console.WriteLine("Stuff 1 did something cool!");
}
public Stuff1()
{
Console.WriteLine("New stuff 1 reporting for duty!");
}
}
public class Stuff2 : StuffBase
{
public void DoSomething()
{
Console.WriteLine("Stuff 2 did something cool!");
}
public Stuff1()
{
Console.WriteLine("New stuff 2 reporting for duty!");
}
}
Bene, ora dì che ho un elenco di elementi:
var items = new List<StuffBase>();
items.Add(new Stuff1());
items.Add(new Stuff2());
e voglio che tutti chiamino il loro metodo DoSomething (). Potrei aspettarmi di iterare la lista e chiamare il loro metodo DoSomething (), quindi diciamo che ho un metodo per fare quello chiamato AllDoSomething () che scorre solo sulla lista e fa il lavoro:
public static void AllDoSomething(List<StuffBase> items)
{
items.ForEach(i => i.DoSomething());
}
Qual è la differenza pratica del seguente metodo?
public static void AllDoSomething<T>(List<T> items) where T: StuffBase
{
items.ForEach(i => i.DoSomething());
}
Entrambi i metodi appaiono in termini reali, sebbene siano sintatticamente diversi, per fare la stessa cosa.
Sono solo modi diversi di fare la stessa cosa? Comprendo i generici e i vincoli di tipo, ma non riesco a capire perché in questo caso dovrei usare un modo rispetto all'altro.
Soluzione
Questo perché C # non supporta ancora Covariance .
Più formalmente, in C # v2.0 se T è a sottotipo di U, quindi T [] è un sottotipo di U [], ma G non è un sottotipo di G (dove G è un tipo generico). Nel terminologia di teoria dei tipi, descriviamo questo comportamento dicendo che l'array C # i tipi sono & # 8220; covariante & # 8221; e generico i tipi sono & # 8220; invariante & # 8221 ;.
Riferimento: http: // blog. msdn.com/rmbyers/archive/2005/02/16/375079.aspx
Se hai il seguente metodo:
public static void AllDoSomething(List<StuffBase> items)
{
items.ForEach(i => i.DoSomething());
}
var items = new List<Stuff2>();
x.AllDoSomething(items); //Does not compile
Dove se si utilizza il vincolo di tipo generico, lo farà.
Per ulteriori informazioni su Covariance e Contravarianza], controlla La serie di post di Eric Lippert .
Altri post che vale la pena leggere:
- http://www.pabich.eu/blog/archive/2008/02/12/c-generics---parameter-variance-its-constraints-and-how-it.aspx
- http://blogs.msdn.com/rmbyers /archive/2006/06/01/613690.aspx
- http://msdn.microsoft.com/ it-it / library / ms228359 (VS.80) aspx
- http://www.csharp411.com/convert-between-generic- ienumerablet /
- http://research.microsoft.com/apps/pubs /default.aspx?id=64042
- Perché non è possibile elencare < parent > = Elenco & Lt; child & Gt ;?
Altri suggerimenti
Supponi di avere un elenco:
List<Stuff1> l = // get from somewhere
Ora prova:
AllDoSomething(l);
Con la versione generica, sarà consentito. Con il non generico, non lo farà. Questa è la differenza essenziale. Un elenco di Stuff1
non è un elenco di StuffBase
. Ma nel caso generico, non è necessario che sia esattamente un elenco di <=>, quindi è più flessibile.
Puoi aggirare il problema copiando prima il tuo elenco di <=> in un elenco di <=>, per renderlo compatibile con la versione non generica. Ma supponiamo che tu abbia un metodo:
List<T> TransformList<T>(List<T> input) where T : StuffBase
{
List<T> output = new List<T>();
foreach (T item in input)
{
// examine item and decide whether to discard it,
// make new items, whatever
}
return output;
}
Senza generici, potresti accettare un elenco di <=>, ma dovresti quindi restituire un elenco di <=>. Il chiamante avrebbe dovuto usare i cast se sapesse che gli oggetti erano davvero di tipo derivato. Quindi i generici ti consentono di preservare il tipo effettivo di un argomento e di canalizzarlo attraverso il metodo al tipo restituito.
Nell'esempio che hai fornito non c'è alcuna differenza, ma prova quanto segue:
List<Stuff1> items = new List<Stuff1>();
items.Add(new Stuff1());
AllDoSomething(items);
AllDoSomething<StuffBase>(items);
La prima chiamata funziona bene ma la seconda non viene compilata a causa della covarianza generica