Ich habe ein wenig Probleme zu verstehen, wie ich Kovarianz und Kontravarianz in der realen Welt verwenden würde.
Bisher waren die einzigen Beispiele, die ich gesehen habe, das gleiche alte Array-Beispiel.
object[] objectArray = new string[] { "string 1", "string 2" };
Es wäre schön, ein Beispiel zu sehen, mit dem ich es während meiner Entwicklung verwenden könnte, wenn ich sehen könnte, dass es an anderer Stelle verwendet wird.
c#
c#-4.0
covariance
Rasierer
quelle
quelle
Antworten:
Angenommen, Sie haben eine Klasse Person und eine Klasse, die sich daraus ableitet, Lehrer. Sie haben einige Operationen, die ein
IEnumerable<Person>
als Argument verwenden. In Ihrer Schulklasse haben Sie eine Methode, die eine zurückgibtIEnumerable<Teacher>
. Mit der Kovarianz können Sie dieses Ergebnis direkt für die Methoden verwenden, dieIEnumerable<Person>
einen weniger abgeleiteten (allgemeineren) Typ durch einen abgeleiteten Typ ersetzen. Kontravarianz ermöglicht es Ihnen, entgegen der Intuition einen allgemeineren Typ zu verwenden, bei dem ein stärker abgeleiteter Typ angegeben wird.Siehe auch Kovarianz und Kontravarianz in Generika auf MSDN .
Klassen :
Verwendung :
quelle
Zur Vollständigkeit…
quelle
void feed(IGobbler<Donkey> dg)
. Wenn Sie stattdessen einen IGobbler <Quadruped> als Parameter verwenden, können Sie keinen Drachen übergeben, der nur Esel frisst.Folgendes habe ich zusammengestellt, um den Unterschied zu verstehen
tldr
quelle
Contravariance
Beispiel) auf Apfel niedergeschlagen werden, wennFruit
der Elternteil von istApple
?Die Schlüsselwörter in und out steuern die Casting-Regeln des Compilers für Schnittstellen und Delegaten mit generischen Parametern:
quelle
Hier ist ein einfaches Beispiel mit einer Vererbungshierarchie.
Angesichts der einfachen Klassenhierarchie:
Und im Code:
Invarianz (dh generische Typparameter * nicht * verziert mit
in
oderout
Schlüsselwörter)Scheinbar eine Methode wie diese
... sollte eine heterogene Sammlung akzeptieren: (was es tut)
Das Übergeben einer Sammlung eines abgeleiteten Typs schlägt jedoch fehl!
Warum? Da der generische Parameter
IList<LifeForm>
nicht kovariant ist - erIList<T>
ist invariant,IList<LifeForm>
akzeptiert er nur Sammlungen (die IList implementieren), in denen der parametrisierte Typ seinT
mussLifeForm
.Wenn die Methodenimplementierung von
PrintLifeForms
böswillig war (aber dieselbe Methodensignatur hat), wird der Grund, warum der Compiler das Übergeben verhindert,List<Giraffe>
offensichtlich:Da
IList
das Hinzufügen oder Entfernen von Elementen möglich ist,LifeForm
könnte dem Parameter eine beliebige Unterklasse von hinzugefügt werdenlifeForms
, die den Typ einer an die Methode übergebenen Sammlung abgeleiteter Typen verletzen würde. (Hier würde die böswillige Methode versuchen, einZebra
zu hinzuzufügenvar myGiraffes
). Glücklicherweise schützt uns der Compiler vor dieser Gefahr.Kovarianz (generisch mit parametrisiertem Typ verziert mit
out
)Kovarianz wird häufig bei unveränderlichen Sammlungen verwendet (dh wenn neue Elemente nicht zu einer Sammlung hinzugefügt oder daraus entfernt werden können).
Die Lösung für das obige Beispiel besteht darin, sicherzustellen, dass ein kovarianter generischer Sammlungstyp verwendet wird, z. B.
IEnumerable
(definiert alsIEnumerable<out T>
).IEnumerable
Es gibt keine Methoden zum Ändern der Sammlung. Aufgrund derout
KovarianzLifeForm
kann jetzt jede Sammlung mit dem Subtyp von an die Methode übergeben werden:PrintLifeForms
kann nun mit genannt werdenZebras
,Giraffes
und jedeIEnumerable<>
von jeder Unterklasse vonLifeForm
Kontravarianz (generisch mit parametrisiertem Typ verziert mit
in
)Kontravarianz wird häufig verwendet, wenn Funktionen als Parameter übergeben werden.
Hier ist ein Beispiel für eine Funktion, die a
Action<Zebra>
als Parameter verwendet und es für eine bekannte Instanz eines Zebras aufruft:Wie erwartet funktioniert dies einwandfrei:
Intuitiv wird dies fehlschlagen:
Dies ist jedoch erfolgreich
und auch das gelingt:
Warum? Weil
Action
definiert ist alsAction<in T>
, dh es istcontravariant
, was bedeutet, dass fürAction<Zebra> myAction
, dasmyAction
kann höchstens a seinAction<Zebra>
, aber weniger abgeleitete Oberklassen vonZebra
sind auch akzeptabel.Obwohl dies zunächst möglicherweise nicht intuitiv ist (z. B. wie kann ein
Action<object>
Parameter als Parameter übergeben werdenAction<Zebra>
?), Werden Sie beim Entpacken der Schritte feststellen, dass die aufgerufene Funktion (PerformZebraAction
) selbst für die Übergabe von Daten verantwortlich ist (in diesem Fall eineZebra
Instanz) ) zur Funktion - die Daten stammen nicht aus dem aufrufenden Code.Aufgrund des umgekehrten Ansatzes, Funktionen höherer Ordnung auf diese Weise zu verwenden, wird zum Zeitpunkt des
Action
Aufrufs derZebra
Instanz die stärker abgeleitete Instanz für diezebraAction
Funktion aufgerufen (als Parameter übergeben), obwohl die Funktion selbst einen weniger abgeleiteten Typ verwendet.quelle
in
Schlüsselwort für die Kontravarianz verwendet ?Action<in T>
undFunc<in T, out TResult>
sind im Eingabetyp kontravariant. (Meine Beispiele verwenden vorhandene invariante (Liste), kovariante (IEnumerable) und kontravariante (Action, Func) Typen)C#
, würde das nicht wissen.Grundsätzlich konnten Sie, wenn Sie eine Funktion hatten, die eine Aufzählung eines Typs verwendet, eine Aufzählung eines abgeleiteten Typs nicht übergeben, ohne sie explizit umzuwandeln.
Nur um Sie vor einer Falle zu warnen:
Das ist sowieso schrecklicher Code, aber er existiert und das sich ändernde Verhalten in C # 4 kann subtile und schwer zu findende Fehler verursachen, wenn Sie ein Konstrukt wie dieses verwenden.
quelle
Von MSDN
quelle
Kontravarianz
In der realen Welt können Sie immer ein Tierheim anstelle eines Kaninchenhauses verwenden, da jedes Mal, wenn ein Tierheim ein Kaninchen beherbergt, es ein Tier ist. Wenn Sie jedoch ein Kaninchenhaus anstelle eines Tierheims verwenden, kann das Personal von einem Tiger gefressen werden.
In Code, bedeutet dies , dass , wenn Sie eine haben ,
IShelter<Animal> animals
können Sie einfach schreiben ,IShelter<Rabbit> rabbits = animals
wenn Sie versprechen , und die VerwendungT
in derIShelter<T>
nur als Methodenparameter wie folgt:und ersetzen Sie einen Artikel durch einen allgemeineren, dh verringern Sie die Varianz oder führen Sie Kontra ein .
Kovarianz
In der realen Welt können Sie immer einen Lieferanten von Kaninchen anstelle eines Lieferanten von Tieren verwenden, da jedes Mal, wenn ein Kaninchenlieferant Ihnen ein Kaninchen gibt, es ein Tier ist. Wenn Sie jedoch einen Tierlieferanten anstelle eines Kaninchenlieferanten verwenden, können Sie von einem Tiger gefressen werden.
Im Code bedeutet dies, dass Sie, wenn Sie eine haben
ISupply<Rabbit> rabbits
, einfach schreiben können,ISupply<Animal> animals = rabbits
wenn Sie versprechen undT
in derISupply<T>
nur als Methode verwendeten Rückgabewerte wie folgt verwenden:und ersetzen Sie einen Gegenstand durch einen abgeleiteten, dh erhöhen Sie die Varianz oder führen Sie eine Co- Varianz ein.
Alles in allem ist dies nur ein überprüfbares Versprechen von Ihnen zur Kompilierungszeit , dass Sie einen generischen Typ auf eine bestimmte Weise behandeln würden, um die Typensicherheit zu gewährleisten und niemanden zum Essen zu bringen.
Vielleicht möchten Sie dies lesen, um Ihren Kopf doppelt darum zu wickeln.
quelle
contravariance
ist interessant. Ich lese darin als Hinweis auf eine betriebliche Anforderung: dass der allgemeinere Typ die Anwendungsfälle aller davon abgeleiteten Typen unterstützen muss. In diesem Fall muss das Tierheim in der Lage sein, die Unterbringung aller Tierarten zu unterstützen. In diesem Fall kann das Hinzufügen einer neuen Unterklasse die Oberklasse beschädigen! Das heißt - wenn wir einen Subtyp Tyrannosaurus Rex hinzufügen , könnte dies unser bestehendes Tierheim zerstören .Der Konverterdelegierte hilft mir, beide Konzepte zusammen zu visualisieren:
TOutput
stellt die Kovarianz dar, bei der eine Methode einen spezifischeren Typ zurückgibt .TInput
stellt eine Kontravarianz dar, bei der eine Methode einem weniger spezifischen Typ übergeben wird .quelle