<out T> vs <T> in Generika


188

Was ist der Unterschied zwischen <out T>und <T>? Beispielsweise:

public interface IExample<out T>
{
    ...
}

vs.

public interface IExample<T>
{
    ...
}

1
Ein gutes Beispiel wäre IObservable <T> und IObserver <T>, definiert in System ns in mscorlib. öffentliche Schnittstelle IObservable <out T> und öffentliche Schnittstelle IObserver <in T>. Ebenso IEnumerator <out T>, IEnumerable <out T>
VivekDev

1
Die beste Erklärung, die ich getroffen habe: agirlamonggeeks.com/2019/05/29/… . (<In T> <- bedeutet, dass T nur als Parameter an eine Methode übergeben werden kann; <out T> <- bedeutet, dass T sein kann nur als Methodenergebnisse zurückgegeben)
Uladzimir Sharyi

Antworten:


210

Das outSchlüsselwort in Generika wird verwendet, um anzuzeigen, dass der Typ T in der Schnittstelle kovariant ist. Siehe Kovarianz und Kontravarianz für Details.

Das klassische Beispiel ist IEnumerable<out T>. Da IEnumerable<out T>es kovariant ist, dürfen Sie Folgendes tun:

IEnumerable<string> strings = new List<string>();
IEnumerable<object> objects = strings;

Die zweite Zeile oben würde fehlschlagen, wenn dies nicht kovariant wäre, obwohl dies logischerweise funktionieren sollte, da die Zeichenfolge vom Objekt abgeleitet ist. Bevor C # und VB.NET (in .NET 4 mit VS 2010) Abweichungen bei generischen Schnittstellen hinzugefügt wurden, war dies ein Fehler bei der Kompilierung.

Nach .NET 4 IEnumerable<T>wurde kovariant markiert und wurde IEnumerable<out T>. Da IEnumerable<out T>nur die darin enthaltenen Elemente verwendet werden und diese niemals hinzugefügt / geändert werden, ist es sicher, eine aufzählbare Sammlung von Zeichenfolgen als eine aufzählbare Sammlung von Objekten zu behandeln, was bedeutet, dass sie kovariant ist .

Dies würde mit einem Typ wie nicht funktionieren IList<T>, da IList<T>es eine AddMethode gibt. Angenommen, dies wäre zulässig:

IList<string> strings = new List<string>();
IList<object> objects = strings;  // NOTE: Fails at compile time

Sie könnten dann anrufen:

objects.Add(new Image()); // This should work, since IList<object> should let us add **any** object

Dies würde natürlich fehlschlagen - IList<T>kann also nicht als kovariant markiert werden.

Es gibt übrigens auch eine Option für in- die von Dingen wie Vergleichsschnittstellen verwendet wird. IComparer<in T>funktioniert zum Beispiel umgekehrt. Sie können einen Beton IComparer<Foo>direkt als IComparer<Bar>if- BarUnterklasse von verwenden Foo, da die IComparer<in T>Schnittstelle kontravariant ist .


4
@ColeJohnson Weil Imagees sich um eine abstrakte Klasse handelt;) Sie können new List<object>() { Image.FromFile("test.jpg") };ohne Probleme arbeiten, oder Sie können es auch tun new List<object>() { new Bitmap("test.jpg") };. Das Problem mit Ihrem ist, dass new Image()nicht erlaubt ist (Sie können auch nicht tun var img = new Image();)
Reed Copsey

4
Ein Generikum IList<object>ist ein bizarres Beispiel. Wenn Sie möchten, objectbrauchen Sie keine Generika.
Jodrell

5
@ReedCopsey Widersprechen Sie nicht Ihrer eigenen Antwort in Ihrem Kommentar?
MarioDS

64

Um sich leicht an die Verwendung von inund das outSchlüsselwort (auch Kovarianz und Kontravarianz) zu erinnern , können wir die Vererbung als Wrapping darstellen:

String : Object
Bar : Foo

rein / raus


11
Ist das nicht der falsche Weg? Mit Contravariance = in = können weniger abgeleitete Typen anstelle von mehr abgeleiteten verwendet werden. / Covariance = out = ermöglicht die Verwendung von mehr abgeleiteten Typen anstelle von weniger abgeleiteten. Wenn ich Ihr Diagramm persönlich betrachte, lese ich es als das Gegenteil davon.
Sam Shiles

co u variante (: für mich
snr

49

Erwägen,

class Fruit {}

class Banana : Fruit {}

interface ICovariantSkinned<out T> {}

interface ISkinned<T> {}

und die Funktionen,

void Peel(ISkinned<Fruit> skinned) { }

void Peel(ICovariantSkinned<Fruit> skinned) { }

Die Funktion , die akzeptiert ICovariantSkinned<Fruit>zu akzeptieren , wird in der Lage ICovariantSkinned<Fruit>oder ICovariantSkinned<Bananna>weil ICovariantSkinned<T>eine kovariante Schnittstelle und Bananaist eine Art Fruit,

Die Funktion, die akzeptiert, ISkinned<Fruit>kann nur akzeptieren ISkinned<Fruit>.


37

" out T" bedeutet, dass der Typ T"kovariant" ist. Dies beschränkt sich Tdarauf, nur als zurückgegebener (ausgehender) Wert in Methoden der generischen Klasse, Schnittstelle oder Methode angezeigt zu werden. Die Implikation ist, dass Sie den Typ / die Schnittstelle / die Methode in ein Äquivalent mit einem Supertyp von umwandeln können T.
ZB ICovariant<out Dog>kann gegossen werden ICovariant<Animal>.


13
Ich habe nicht bemerkt, dass outVollstreckungen Tnur zurückgegeben werden können, bis ich diese Antwort gelesen habe. Das ganze Konzept macht jetzt mehr Sinn!
MarioDS

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.