Frage

Kann mir jemand erklären, das Konzept der Kovarianz und der Verträge in der Theorie der Programmiersprachen?

War es hilfreich?

Lösung

Kovarianz ist ziemlich einfach und am besten aus der Perspektive einer Sammlungsklasse angemessen List. Wir können Parametrize das List Klasse mit einem Typ Parameter T. Das heißt, unsere Liste enthält Elemente vom Typ T für einige T. Liste wäre kovarant, wenn

S ist ein Subtyp der TIFF -Liste [s] ist ein Subtyp der Liste [t

(Wo ich die mathematische Definition verwende IFF meinen dann und nur dann, wenn.)

Das ist ein List[Apple] ist ein List[Fruit]. Wenn es eine Routine gibt, die a akzeptiert List[Fruit] als Parameter, und ich habe a List[Apple], Dann kann ich dies als gültiger Parameter übergeben.

def something(l: List[Fruit]) {
    l.add(new Pear())
}

Wenn unsere Sammlungsklasse List ist veränderlich, dann macht Kovarianz keinen Sinn, weil wir annehmen könnten, dass unsere Routine andere Früchte (was kein Apfel war) wie oben hinzufügen könnte. Daher sollten wir nur mögen unveränderlich Sammlungskurse, um kovarant zu sein!

Andere Tipps

Hier sind meine Artikel darüber, wie wir C# 4.0 neue Varianzfunktionen hinzugefügt haben. Beginnen Sie von unten.

http://blogs.msdn.com/ericlippert/archive/tags/covarianz+and+contravariation/default.aspx

Es gibt eine Unterscheidung zwischen dazwischen Kovarianz und Kontravarianz.
Sehr grob ist eine Operation kovarianten, wenn sie die Reihenfolge von Typen bewahrt, und kontravariante, wenn es sich kehrt um diese Bestellung.

Die Ordnung selbst soll allgemeinere Typen als größer als spezifischer darstellen.
Hier ist ein Beispiel für eine Situation, in der C# die Kovarianz unterstützt. Erstens ist dies eine Reihe von Objekten:

object[] objects=new object[3];
objects[0]=new object();
objects[1]="Just a string";
objects[2]=10;

Natürlich ist es möglich, verschiedene Werte in das Array einzufügen, da sie am Ende alle abgeleitet sind System.Object In .NET Framework. Mit anderen Worten, System.Object ist ein sehr allgemeiner oder groß Typ. Hier ist ein Ort, an dem die Kovarianz unterstützt wird:
Zuweisen eines Wertes eines kleineren Typs zu einer Variablen eines größeren Typs

string[] strings=new string[] { "one", "two", "three" };
objects=strings;

Die vom Typ variablen Objekte object[], kann einen Wert speichern, der tatsächlich vom Typ ist string[].

Denken Sie darüber nach - bis zu einem gewissen Punkt ist es das, was Sie erwarten, aber andererseits ist es nicht. Immerhin während string kommt von object, string[] NICHT ergeben sich aus object[]. Die Sprachunterstützung für die Kovarianz in diesem Beispiel ermöglicht die Aufgabe sowieso, was Sie in vielen Fällen finden werden. Varianz ist eine Funktion, die die Sprache intuitiver funktioniert.

Die Überlegungen zu diesen Themen sind äußerst kompliziert. Basierend auf dem vorhergehenden Code finden Sie beispielsweise zwei Szenarien, die zu Fehlern führen.

// Runtime exception here - the array is still of type string[],
// ints can't be inserted
objects[2]=10;

// Compiler error here - covariance support in this scenario only
// covers reference types, and int is a value type
int[] ints=new int[] { 1, 2, 3 };
objects=ints;

Ein Beispiel für die Arbeitsweise der Kontravarianz ist etwas komplizierter. Stellen Sie sich diese beiden Klassen vor:

public partial class Person: IPerson {
    public Person() {
    }
}

public partial class Woman: Person {
    public Woman() {
    }
}

Woman ist abgeleitet von Person, offensichtlich. Denken Sie jetzt daran, dass Sie diese beiden Funktionen haben:

static void WorkWithPerson(Person person) {
}

static void WorkWithWoman(Woman woman) {
}

Eine der Funktionen macht etwas (es ist egal was) mit einem Woman, der andere ist allgemeiner und kann mit jedem Typ arbeiten, der abgeleitet ist Person. Auf der Woman Seite der Dinge, Sie haben jetzt auch diese:

delegate void AcceptWomanDelegate(Woman person);

static void DoWork(Woman woman, AcceptWomanDelegate acceptWoman) {
    acceptWoman(woman);
}

DoWork ist eine Funktion, die a nehmen kann Woman und ein Hinweis auf eine Funktion, die auch a dauert Woman, und dann passt es die Instanz von Woman zum Delegierten. Bedenke die Polymorphismus der Elemente, die Sie hier haben. Person ist größer als Woman, und WorkWithPerson ist größer als WorkWithWoman. WorkWithPerson wird auch berücksichtigt größer als AcceptWomanDelegate zum Zweck der Varianz.

Schließlich haben Sie diese drei Codezeilen:

Woman woman=new Woman();
DoWork(woman, WorkWithWoman);
DoWork(woman, WorkWithPerson);

EIN Woman Instanz wird erstellt. Dann wird Dowork angerufen und in der vorbeikommt Woman Instanz sowie ein Hinweis auf die WorkWithWoman Methode. Letzteres ist offensichtlich mit dem Delegierten -Typ kompatibel AcceptWomanDelegate - Ein Parameter des Typs Woman, kein Rückgabetyp. Die dritte Zeile ist jedoch etwas seltsam. Die Methode WorkWithPerson nimmt ein Person als Parameter, nicht a Woman, wie benötigt von AcceptWomanDelegate. Nichtsdestotrotz, WorkWithPerson ist mit dem Delegierten -Typ kompatibel. Kontravarianz ermöglicht es möglich, also bei Delegierten der größere Typ WorkWithPerson kann in einer Variablen des kleineren Typs gespeichert werden AcceptWomanDelegate. Noch einmal ist es die intuitive Sache: wenn WorkWithPerson kann mit jedem arbeiten Person, pass in a Woman Ich kann mich nicht irren, Rechts?

Inzwischen fragen Sie sich vielleicht, wie all dies auf Generika zusammenhängt. Die Antwort ist, dass auch Varianz auf Generika angewendet werden kann. Das vorhergehende Beispiel verwendet object und string Arrays. Hier verwendet der Code generische Listen anstelle der Arrays:

List<object> objectList=new List<object>();
List<string> stringList=new List<string>();
objectList=stringList;

Wenn Sie dies ausprobieren, werden Sie feststellen, dass dies kein unterstütztes Szenario in C#ist. In C# Version 4.0 sowie .NET Framework 4.0 wurde die Varianzunterstützung in Generics gereinigt und es ist jetzt möglich, die neuen Schlüsselwörter zu verwenden in und aus mit generischen Typparametern. Sie können die Richtung des Datenflusss für einen bestimmten Typparameter definieren und einschränken, sodass die Funktionsweise funktioniert. Aber im Fall von List<T>, die Daten vom Typ T fließt in beide Richtungen - es gibt Methoden auf dem Typ List<T> das kehrt zurück T Werte und andere, die solche Werte erhalten.

Der Punkt dieser Richtungsbeschränkungen ist Um eine Varianz zu ermöglichen, wo es Sinn macht, aber zu Probleme verhindern Wie der Laufzeitfehler in einem der vorherigen Array -Beispiele. Wenn Typparameter korrekt dekoriert sind mit in oder aus, der Compiler kann seine Abweichung bei überprüfen und zulassen oder nicht zulassen Zeit kompilieren. Microsoft hat sich bemüht, diese Keywords zu vielen Standardschnittstellen in .NET Framework hinzuzufügen, wie IEnumerable<T>:

public interface IEnumerable<out T>: IEnumerable {
    // ...
}

Für diese Schnittstelle der Datenfluss vom Typ T Objekte sind klar: Sie können immer nur aus Methoden abgerufen werden, die von dieser Schnittstelle unterstützt werden und nicht in sie übergeben werden. Infolgedessen ist es möglich, ein ähnliches Beispiel wie das zu konstruieren List<T> Versuch zuvor beschrieben, aber verwenden IEnumerable<T> :

IEnumerable<object> objectSequence=new List<object>();
IEnumerable<string> stringSequence=new List<string>();
objectSequence=stringSequence;

Dieser Code ist für den C# Compiler seit Version 4.0 akzeptabel, weil IEnumerable<T> ist kovariante aufgrund der aus Spezifizierer für den Typparameter T.

Bei der Arbeit mit generischen Typen ist es wichtig, sich der Varianz und der Art und Weise bewusst zu sein, wie der Compiler verschiedene Arten von Tricks anwendet, damit Ihr Code so funktioniert, wie Sie es erwarten.

Es gibt mehr über Varianz zu wissen, als in diesem Kapitel behandelt wird, aber dies ausreicht, um alle weiteren Code verständlich zu machen.

Ref:

Bart de SMET hat einen großartigen Blogeintrag über Kovarianz und Kontravarianz hier.

Sowohl C# als auch die CLR ermöglichen die Kovarianz und Kontra-Varianz von Referenztypen, wenn sie eine Methode an einen Delegierten binden. Kovarianz bedeutet, dass eine Methode einen Typ zurückgeben kann, der vom Rückgabetyp des Delegierten abgeleitet wird. Contra-Varianz bedeutet, dass eine Methode einen Parameter annehmen kann, der eine Basis des Parametertyps des Delegiertens ist. Zum Beispiel bei einem so definierten Delegierten wie folgt:

Delegierter Objekt MyCallback (FileStream S);

Es ist möglich, eine Instanz dieses Delegiertenstyps zu konstruieren, das an eine prototypische Methode gebunden ist

so was:

String Somemethod (Stream S);

Hier ist der Rückgabetyp (String) von Somemethod ein Typ, der vom Rückgabetyp des Delegierten (Objekt) abgeleitet wird. Diese Kovarianz ist erlaubt. Der Parametertyp (Stream) von Somemethod ist ein Typ, der eine Basisklasse des Parametertyps des Delegierten (FileStream) ist. Diese Kontra-Varianz ist erlaubt.

Beachten Sie, dass Kovarianz und Kontra-Varianz nur für Referenztypen, nicht für Werttypen oder für Leere unterstützt werden. So kann ich beispielsweise die folgende Methode nicht an den MyCallback -Delegierter binden:

INT32 einigeothermethoden (Stream S);

Obwohl der Rückgabetyp eines anderen Methods (Int32) von MyCallbacks Rückgabetyp (Objekt) abgeleitet ist, ist diese Form der Kovarianz nicht zulässig, da INT32 ein Werttyp ist.

Der Grund, warum Werttypen und Leere nicht für Kovarianz und Kontra-Varianz verwendet werden können, liegt darin, dass die Speicherstruktur für diese Dinge variiert, während die Speicherstruktur für Referenztypen immer Zeiger ist. Glücklicherweise erzeugt der C# Compiler einen Fehler, wenn Sie versuchen, etwas zu tun, das nicht unterstützt wird.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top