2010-01-09 5 views
44

Schauen Sie sich das folgende Beispiel (teilweise aus MSDN Blog genommen):C# Varianz Problem: Zuordnung Liste <Derived> als Liste <Base>

class Animal { } 
class Giraffe : Animal { } 

static void Main(string[] args) 
{ 
    // Array assignment works, but... 
    Animal[] animals = new Giraffe[10]; 

    // implicit... 
    List<Animal> animalsList = new List<Giraffe>(); 

    // ...and explicit casting fails 
    List<Animal> animalsList2 = (List<Animal>) new List<Giraffe>(); 
} 

Ist das ein Kovarianz-Problem? Wird dies in der zukünftigen C# Version unterstützt und gibt es clevere Workarounds (nur mit .NET 2.0)?

Antwort

95

dies Nun schon gar nicht in C# unterstützt werden 4. Es gibt ein grundlegendes Problem ist:

List<Giraffe> giraffes = new List<Giraffe>(); 
giraffes.Add(new Giraffe()); 
List<Animal> animals = giraffes; 
animals.Add(new Lion()); // Aargh! 

Halten Giraffen sicher: nur nein sagen zu unsicher Varianz.

Die Array-Version funktioniert, weil Arrays do Referenzvarianz unterstützen, mit Laufzeitprüfung. Der Punkt der Generika ist Kompilierungszeit Typ Sicherheit.

In C# 4 gibt es Unterstützung für sichere generische Varianz, aber nur für Schnittstellen und Delegaten. So werden Sie in der Lage zu tun:

Func<string> stringFactory =() => "always return this string"; 
Func<object> objectFactory = stringFactory; // Safe, allowed in C# 4 

Func<out T> ist covariant in T weil T nur in einer Ausgangsposition verwendet. Vergleichen Sie das mit Action<in T> der kontra ist in T weil T nur in einer Eingabeposition dort verwendet wird, so dass diese sicher:

Action<object> objectAction = x => Console.WriteLine(x.GetHashCode()); 
Action<string> stringAction = objectAction; // Safe, allowed in C# 4 

IEnumerable<out T> ist covariant als auch, so dass diese korrekt in C# 4, wie von anderen darauf hingewiesen:

IEnumerable<Animal> animals = new List<Giraffe>(); 
// Can't add a Lion to animals, as `IEnumerable<out T>` is a read-only interface. 

in Bezug auf die in Ihrer Situation in C# 2, um dieses arbeiten, tun Sie eine Liste zu halten brauchen, oder würden Sie gerne eine neue Liste zu erstellen? Wenn das akzeptabel ist, ist List<T>.ConvertAll dein Freund.

+0

+1 nur um Jons Antwort hinzuzufügen (nicht, dass er irgendeine Hilfe braucht), dem "Func ' '' '' Func 'Beispiel folgend und der Tatsache, dass' T' kontravariant in 'Action' ist. Die folgende 'Aktion = Aktion ' würde in C# 4 nicht funktionieren. –

+0

Danke, dass Sie auch auf das Typsicherheitsproblem hingewiesen haben. Das ist sehr hilfreich. – AndiDog

+0

Was stimmt nicht mit 'animals.Add (new Lion()); // Aargh! '? Wenn du einen 'Löwen' wirken und ihn als' Tier' verwenden kannst, und wenn du alle Elemente in 'Tiere' als 'Tier' verwendest, was ist dann das Problem? – Jeff

11

Es funktioniert in C# 4 für IEnumerable<T>, so können Sie dies tun:

IEnumerable<Animal> animals = new List<Giraffe>(); 

jedoch List<T> ist kein covarient Vorsprung, so dass Sie keine Listen zuordnen können, wie Sie oben getan haben, da Sie dies tun könnte:

List<Animal> animals = new List<Giraffe>(); 
animals.Add(new Monkey()); 

Das ist eindeutig nicht gültig.

6

In Bezug auf List<T>, ich fürchte, Sie haben Pech gehabt. .NET 4.0/C# 4.0 fügt jedoch Unterstützung für kovariante/kontravariante Schnittstellen hinzu. Genauer gesagt ist IEnumerable<T> jetzt als IEnumerable<out T> definiert, was bedeutet, dass der Typparameter jetzt kovariant ist.

Dies bedeutet, dass Sie so etwas wie dies in C# 4.0 ...

// implicit casting 
IEnumerable<Animal> animalsList = new List<Giraffe>(); 

// explicit casting 
IEnumerable<Animal> animalsList2 = (IEnumerable<Animal>) new List<Giraffe>(); 

Hinweis tun können: auch (zumindest seit .NET 1.1) kovariant wurden Array-Typen.

Ich denke, es ist eine Schande, dass die Varianzunterstützung wurde nicht für IList<T> und andere ähnliche generische Schnittstellen (oder generische Klassen sogar) hinzugefügt, aber na ja, zumindest haben wir etwas.

+7

Sie können IList kovariant nicht sicher mit deklarationsseitigen Varianzannotationen erstellen. –

4

Kovarianz/Kontravarianz kann nicht auf veränderlichen Sammlungen unterstützt werden, wie andere bereits erwähnt haben, da es unmöglich ist, die Typsicherheit beider Wege zur Kompilierungszeit zu garantieren; jedoch ist es möglich, eine schnelle Einweg Umwandlung in C# 3.5, zu tun, wenn das ist, was Sie suchen:

List<Giraffe> giraffes = new List<Giraffe>(); 
List<Animal> animals = giraffes.Cast<Animal>().ToList(); 

Natürlich ist es nicht das gleiche, es ist nicht wirklich Kovarianz - du bist tatsächlich eine andere Liste erstellen, aber es ist sozusagen ein "Workaround".

In .NET 2.0 können Sie die Vorteile von Matrixkovarianz nehmen Sie den Code zu vereinfachen:

List<Giraffe> giraffes = new List<Giraffe>(); 
List<Animal> animals = new List<Animal>(giraffes.ToArray()); 

aber bewusst sein, dass Sie tatsächlich sind zwei neue Kollektionen zu schaffen.

+0

Ich denke, das sind ziemlich gute Problemumgehungen für meine einfache Anwendung. Zumindest sind sie gut für die Lesbarkeit - nicht für die Leistung. – AndiDog

+0

@JohnAskew: Ich bin mir nicht sicher, was Ihr Punkt hier ist - es gibt keine 'Cast' Methode auf' IList ', es ist die' Enumerable .Cast 'Erweiterung Methode, die ein' IEnumerable 'nimmt und explizit jede Element zu 'T2' und gibt es dann zurück (als 'IEnumerable ', nicht ein' IList '). Es hat überhaupt nichts mit "IList " zu tun, abgesehen von der Tatsache, dass 'IList ' zufälligerweise von 'IEnumerable 'erbt und somit die' Enumerable ' Erweiterungsmethoden unterstützt. Da ist keine Kovarianz. – Aaronaught

+0

Hoppla ... Ich klickte auf Löschen statt auf Bearbeiten: -S –

Verwandte Themen