Grundlegendes zu kovarianten und kontravarianten Schnittstellen in C #


85

Ich bin auf diese in einem Lehrbuch gestoßen, das ich auf C # lese, aber ich habe Schwierigkeiten, sie zu verstehen, wahrscheinlich aufgrund mangelnden Kontexts.

Gibt es eine gute, präzise Erklärung dafür, was sie sind und wofür sie nützlich sind?

Zur Verdeutlichung bearbeiten:

Kovariante Schnittstelle:

interface IBibble<out T>
.
.

Kontravariante Schnittstelle:

interface IBibble<in T>
.
.

3
Dies ist eine kurze und gute Erklärung IMHO: blogs.msdn.com/csharpfaq/archive/2010/02/16/…
digEmAll

Kann

Hmm, es ist gut, aber es erklärt nicht, warum mich das wirklich verblüfft.
NibblyPig

Antworten:


143

Mit <out T>können Sie die Schnittstellenreferenz als eine in der Hierarchie nach oben behandeln.

Mit <in T>können Sie die Schnittstellenreferenz in der Hiearchy als eine nach unten behandeln.

Lassen Sie mich versuchen, es in mehr englischen Begriffen zu erklären.

Angenommen, Sie rufen eine Liste der Tiere aus Ihrem Zoo ab und beabsichtigen, sie zu verarbeiten. Alle Tiere (in Ihrem Zoo) haben einen Namen und eine eindeutige ID. Einige Tiere sind Säugetiere, einige sind Reptilien, einige sind Amphibien, einige sind Fische usw., aber sie sind alle Tiere.

Mit Ihrer Liste von Tieren (die Tiere verschiedener Arten enthält) können Sie also sagen, dass alle Tiere einen Namen haben, so dass es offensichtlich sicher wäre, den Namen aller Tiere zu erhalten.

Was ist jedoch, wenn Sie nur eine Liste von Fischen haben, diese aber wie Tiere behandeln müssen? Funktioniert das? Intuitiv sollte es funktionieren, aber in C # 3.0 und früher wird dieser Code nicht kompiliert:

IEnumerable<Animal> animals = GetFishes(); // returns IEnumerable<Fish>

Der Grund dafür ist, dass der Compiler nicht "weiß", was Sie mit der Tiersammlung beabsichtigen oder tun können , nachdem Sie sie abgerufen haben. Nach allem, was es weiß, könnte es einen Weg geben IEnumerable<T>, ein Objekt wieder in die Liste aufzunehmen, und dies würde es Ihnen möglicherweise ermöglichen, ein Tier, das kein Fisch ist, in eine Sammlung aufzunehmen, die nur Fisch enthalten soll.

Mit anderen Worten, der Compiler kann nicht garantieren, dass dies nicht zulässig ist:

animals.Add(new Mammal("Zebra"));

Der Compiler weigert sich also sofort, Ihren Code zu kompilieren. Das ist Kovarianz.

Schauen wir uns die Kontravarianz an.

Da unser Zoo mit allen Tieren umgehen kann, kann er mit Sicherheit auch mit Fischen umgehen. Versuchen wir also, unserem Zoo einige Fische hinzuzufügen.

In C # 3.0 und früheren Versionen wird dies nicht kompiliert:

List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal>
fishes.Add(new Fish("Guppy"));

Hier könnte der Compiler diesen Code zulassen, obwohl die Methode List<Animal>einfach zurückgegeben wird, weil alle Fische Tiere sind. Wenn wir also nur die Typen dahingehend geändert haben:

List<Animal> fishes = GetAccessToFishes();
fishes.Add(new Fish("Guppy"));

Dann würde es funktionieren, aber der Compiler kann nicht feststellen, dass Sie dies nicht versuchen:

List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal>
Fish firstFist = fishes[0];

Da es sich bei der Liste tatsächlich um eine Liste von Tieren handelt, ist dies nicht zulässig.

Kontra- und Co-Varianz ist also, wie Sie Objektreferenzen behandeln und was Sie damit machen dürfen.

Die Schlüsselwörter inund outin C # 4.0 kennzeichnen die Schnittstelle speziell als die eine oder andere. Mit inkönnen Sie den generischen Typ (normalerweise T) in Eingabepositionen einfügen, dh Methodenargumente und Nur-Schreib-Eigenschaften.

Mit outkönnen Sie den generischen Typ in Ausgabepositionen platzieren, dh Methodenrückgabewerte, schreibgeschützte Eigenschaften und Out-Methodenparameter.

Auf diese Weise können Sie das tun, was mit dem Code beabsichtigt ist:

IEnumerable<Animal> animals = GetFishes(); // returns IEnumerable<Fish>
// since we can only get animals *out* of the collection, every fish is an animal
// so this is safe

List<T> hat sowohl In- als auch Out-Richtungen auf T, es ist also weder eine Co-Variante noch eine Contra-Variante, sondern eine Schnittstelle, mit der Sie Objekte wie das folgende hinzufügen können:

interface IWriteOnlyList<in T>
{
    void Add(T value);
}

würde Ihnen erlauben, dies zu tun:

IWriteOnlyList<Fish> fishes = GetWriteAccessToAnimals(); // still returns
                                                            IWriteOnlyList<Animal>
fishes.Add(new Fish("Guppy")); <-- this is now safe

Hier sind einige Videos, die die Konzepte zeigen:

Hier ist ein Beispiel:

namespace SO2719954
{
    class Base { }
    class Descendant : Base { }

    interface IBibbleOut<out T> { }
    interface IBibbleIn<in T> { }

    class Program
    {
        static void Main(string[] args)
        {
            // We can do this since every Descendant is also a Base
            // and there is no chance we can put Base objects into
            // the returned object, since T is "out"
            // We can not, however, put Base objects into b, since all
            // Base objects might not be Descendant.
            IBibbleOut<Base> b = GetOutDescendant();

            // We can do this since every Descendant is also a Base
            // and we can now put Descendant objects into Base
            // We can not, however, retrieve Descendant objects out
            // of d, since all Base objects might not be Descendant
            IBibbleIn<Descendant> d = GetInBase();
        }

        static IBibbleOut<Descendant> GetOutDescendant()
        {
            return null;
        }

        static IBibbleIn<Base> GetInBase()
        {
            return null;
        }
    }
}

Ohne diese Markierungen könnte Folgendes kompiliert werden:

public List<Descendant> GetDescendants() ...
List<Base> bases = GetDescendants();
bases.Add(new Base()); <-- uh-oh, we try to add a Base to a Descendant

oder dieses:

public List<Base> GetBases() ...
List<Descendant> descendants = GetBases(); <-- uh-oh, we try to treat all Bases
                                               as Descendants

Hmm, könnten Sie das Ziel von Kovarianz und Kontravarianz erklären? Es könnte mir helfen, es besser zu verstehen.
NibblyPig

1
Sehen Sie sich das letzte Bit an, das der Compiler zuvor verhindert hat. Der Zweck von In und Out besteht darin, zu sagen, was Sie mit den sicheren Schnittstellen (oder Typen) tun können, damit der Compiler Sie nicht daran hindert, sichere Dinge zu tun .
Lasse V. Karlsen

Hervorragende Antwort, ich habe mir die Videos angesehen, die sehr hilfreich waren, und zusammen mit Ihrem Beispiel habe ich es jetzt sortiert. Es bleibt nur eine Frage offen, und deshalb sind "out" und "in" erforderlich. Warum weiß Visual Studio nicht automatisch, was Sie versuchen (oder was ist der Grund dafür)?
NibblyPig

Automagic "Ich verstehe, was Sie dort versuchen" wird normalerweise verpönt, wenn es darum geht, Dinge wie Klassen zu deklarieren. Es ist besser, wenn der Programmierer die Typen explizit markiert. Sie können versuchen, einer Klasse mit Methoden, die T zurückgeben, "in" hinzuzufügen, und der Compiler wird sich beschweren. Stellen Sie sich vor, was passieren würde, wenn das zuvor automatisch für Sie automatisch hinzugefügte "In" entfernt würde.
Lasse V. Karlsen

1
Wenn ein Schlüsselwort diese lange Erklärung benötigt, stimmt etwas eindeutig nicht. Meiner Meinung nach versucht C # in diesem speziellen Fall zu klug zu sein. Trotzdem danke für die coole Erklärung.
rr-

7

Dieser Beitrag ist der beste, den ich zu diesem Thema gelesen habe

Kurz gesagt, Kovarianz / Kontravarianz / Invarianz befasst sich mit automatischem Typgießen (von der Basis zur abgeleiteten und umgekehrt). Diese Typumwandlungen sind nur möglich, wenn einige Garantien in Bezug auf Lese- / Schreibaktionen, die an den gegossenen Objekten ausgeführt werden, eingehalten werden. Lesen Sie den Beitrag für weitere Details.


5
Link erscheint tot. Hier ist eine archivierte Version: web.archive.org/web/20140626123445/http://adamnathan.co.uk/…
si618

1
Ich mag diese Erklärung noch mehr: codepureandsimple.com/…
volkit
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.