<out T> vs <T> w Generics


189

Jaka jest różnica między <out T>i <T>? Na przykład:

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

vs.

public interface IExample<T>
{
    ...
}

1
Dobrym przykładem byłyby IObservable <T> i IObserver <T>, zdefiniowane w systemie ns w mscorlib. interfejs publiczny IObservable <out T> i interfejs publiczny IObserver <in T>. Podobnie IEnumerator <out T>, IEnumerable <out T>
VivekDev

2
Najlepsze wyjaśnienie, które spotkałem: agirlamonggeeks.com/2019/05/29 / .... (<W T> <- oznacza, że ​​T można przekazać tylko jako parametr do metody; <out T> <- oznacza, że ​​T może być wrócił tylko jako wyniki metody)
Uladzimir Sharyi

Odpowiedzi:


212

outKluczowe w generycznych jest używana do określenia, że typ T w interfejsie jest kowariantna. Zobacz Kowariancja i kontrawariancja, aby uzyskać szczegółowe informacje.

Klasycznym przykładem jest IEnumerable<out T>. Ponieważ IEnumerable<out T>jest to kowariant, możesz wykonać następujące czynności:

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

Druga linia powyżej nie powiodłaby się, gdyby nie była to zmienna kowariantna, chociaż logicznie powinna działać, ponieważ łańcuch pochodzi od obiektu. Przed dodaniem wariancji w ogólnych interfejsach do C # i VB.NET (w .NET 4 z VS 2010) był to błąd czasu kompilacji.

Po .NET 4 IEnumerable<T>został oznaczony jako kowariant i stał się IEnumerable<out T>. Ponieważ IEnumerable<out T>używa tylko zawartych w nim elementów i nigdy ich nie dodaje / nie zmienia, można bezpiecznie traktować wymienną kolekcję ciągów jako wymienną kolekcję obiektów, co oznacza, że ​​jest ona kowariantna .

Nie działałoby to z typem podobnym IList<T>, ponieważ IList<T>ma Addmetodę. Załóżmy, że byłoby to dozwolone:

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

Możesz wtedy zadzwonić:

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

To by się oczywiście nie powiodło - więc IList<T>nie można tego nazwać kowariantem.

Istnieje również opcja dla in- która jest używana przez takie rzeczy jak interfejsy porównawcze. IComparer<in T>, na przykład działa odwrotnie. Betonu można użyć IComparer<Foo>bezpośrednio jako podklasy IComparer<Bar>if , ponieważ interfejs jest sprzeczny .BarFooIComparer<in T>


4
@ColeJohnson Ponieważ Imagejest to klasa abstrakcyjna;) Możesz zrobić to new List<object>() { Image.FromFile("test.jpg") };bez problemów lub możesz zrobić to new List<object>() { new Bitmap("test.jpg") };samo. Problem z twoim jest taki, że new Image()nie jest dozwolony (ty też nie możesz tego zrobić var img = new Image();)
Reed Copsey,

4
ogólny IList<object>to dziwny przykład, jeśli chcesz object, nie potrzebujesz ogólnych .
Jodrell,

5
@ReedCopsey Czy nie zaprzeczasz własnej odpowiedzi w swoim komentarzu?
MarioDS,

64

Aby łatwo zapamiętać użycie ini outsłowa kluczowego (również kowariancji i kontrawariancji), możemy obrazować dziedziczenie jako opakowanie:

String : Object
Bar : Foo

wejście / wyjście


11
Czy to nie jest źle? Kontrawariancja = in = pozwala na stosowanie mniej pochodnych typów zamiast bardziej pochodnych. / Covariance = out = pozwala na stosowanie większej liczby typów pochodnych zamiast mniej pochodnych. Osobiście, patrząc na twój schemat, czytam go jako przeciwieństwo tego.
Sam Shiles,

co u wariant (: dla mnie
snr

49

rozważać,

class Fruit {}

class Banana : Fruit {}

interface ICovariantSkinned<out T> {}

interface ISkinned<T> {}

i funkcje,

void Peel(ISkinned<Fruit> skinned) { }

void Peel(ICovariantSkinned<Fruit> skinned) { }

Funkcja, która akceptuje, ICovariantSkinned<Fruit>będzie w stanie ją zaakceptować ICovariantSkinned<Fruit>lub ICovariantSkinned<Bananna>ponieważ ICovariantSkinned<T>jest interfejsem kowariantnym i Bananajest rodzajem Fruit,

funkcja, która akceptuje, ISkinned<Fruit>będzie mogła tylko zaakceptować ISkinned<Fruit>.


38

out T” oznacza, że ​​typ Tjest „kowariantny”. Ogranicza Tto wyświetlanie tylko jako wartość zwracaną (wychodzącą) w metodach ogólnej klasy, interfejsu lub metody. Implikacją jest to, że można rzutować typ / interfejs / metodę na odpowiednik z supertypem T.
Np. ICovariant<out Dog>Można rzucić na ICovariant<Animal>.


14
Nie zdawałem sobie sprawy z tego, że outegzekwowania Tmożna zwrócić tylko, dopóki nie przeczytam tej odpowiedzi. Cała koncepcja ma teraz większy sens!
MarioDS,

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.