<out T> vs <T> em genéricos

188

Qual é a diferença entre <out T>e <T>? Por exemplo:

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

vs.

public interface IExample<T>
{
    ...
}
Cole Johnson
fonte
1
Um bom exemplo seria IObservable <T> e IObserver <T>, definidos no sistema ns no mscorlib. interface pública IObservable <out T> e interface pública IObserver <em T>. Da mesma forma, IEnumerator <fora T>, IEnumerable <fora T>
VivekDev 6/16
1
A melhor explicação que conheci: agirlamonggeeks.com/2019/05/29 . (<Em T> <- significa que T só pode ser passado como parâmetro para um método; <out T> <- significa que T pode ser só voltou como resultados do método)
Uladzimir Sharyi

Respostas:

210

A outpalavra-chave em genéricos é usada para indicar que o tipo T na interface é covariante. Consulte Covariância e contravariância para obter detalhes.

O exemplo clássico é IEnumerable<out T>. Como IEnumerable<out T>é covariante, você pode fazer o seguinte:

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

A segunda linha acima falharia se isso não fosse covariante, mesmo que logicamente funcione, pois a string deriva do objeto. Antes de a variação nas interfaces genéricas ser adicionada ao C # e ao VB.NET (no .NET 4 com VS 2010), era um erro de tempo de compilação.

Após o .NET 4, IEnumerable<T>foi marcado covariante e tornou-se IEnumerable<out T>. Como IEnumerable<out T>apenas usa os elementos contidos nele e nunca os adiciona / altera, é seguro tratar uma coleção enumerável de strings como uma coleção enumerável de objetos, o que significa que é covariante .

Isso não funcionaria com um tipo como IList<T>, pois IList<T>possui um Addmétodo Suponha que isso seja permitido:

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

Você pode ligar para:

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

Obviamente, isso falharia - portanto, IList<T>não pode ser marcado como covariante.

Também existe uma opção para in- que é usada por coisas como interfaces de comparação. IComparer<in T>, por exemplo, funciona da maneira oposta. Você pode usar um concreto IComparer<Foo>diretamente como um IComparer<Bar>if Baré uma subclasse de Foo, porque a IComparer<in T>interface é contravariante .

Reed Copsey
fonte
4
@ColeJohnson Porque Imageé uma classe abstrata;) Você pode fazer new List<object>() { Image.FromFile("test.jpg") };sem problemas ou new List<object>() { new Bitmap("test.jpg") };também. O problema com o seu é que new Image()não é permitido (você não pode fazer var img = new Image();qualquer um)
Reed Copsey
4
um genérico IList<object>é um exemplo bizarro, se você deseja objects, não precisa de genéricos.
Jodrell
5
@ReedCopsey Você não está contradizendo sua própria resposta em seu comentário?
MarioDS 21/09
64

Para lembrar facilmente o uso ine a outpalavra - chave (também covariância e contravariância), podemos imaginar a herança como quebra automática:

String : Object
Bar : Foo

in / out

o0omycomputero0o
fonte
11
Não é este o caminho errado? Contravariância = in = permite que tipos menos derivados sejam usados ​​no lugar de mais derivados. / Covariância = fora = permite que mais tipos derivados sejam usados ​​no lugar de menos derivados. Pessoalmente, olhando para o seu diagrama, eu o li como o oposto disso.
Sam Shiles
co u variant (: para mim
snr 20/03
49

considerar,

class Fruit {}

class Banana : Fruit {}

interface ICovariantSkinned<out T> {}

interface ISkinned<T> {}

e as funções,

void Peel(ISkinned<Fruit> skinned) { }

void Peel(ICovariantSkinned<Fruit> skinned) { }

A função que aceita ICovariantSkinned<Fruit>poderá aceitar ICovariantSkinned<Fruit>ou ICovariantSkinned<Bananna>porque ICovariantSkinned<T>é uma interface covariante e Bananaé um tipo deFruit ,

a função que aceita ISkinned<Fruit>somente poderá aceitar ISkinned<Fruit>.

Jodrell
fonte
37

" out T" significa que o tipo Té "covariante". Isso restringe Ta aparecer apenas como um valor retornado (de saída) nos métodos da classe, interface ou método genérico. A implicação é que você pode converter o tipo / interface / método em um equivalente com um supertipo de T.
Por exemplo, ICovariant<out Dog>pode ser lançado para ICovariant<Animal>.

James World
fonte
13
Eu não percebi que outreforças que Tpodem ser retornadas apenas, até que eu li esta resposta. Todo o conceito faz mais sentido agora!
MarioDS 21/09
6

No link que você postou ....

Para parâmetros de tipo genérico, a palavra-chave out especifica que o parâmetro type é covariante .

EDIT : novamente, a partir do link que você postou

Para obter mais informações, consulte Covariância e contravariância (C # e Visual Basic). http://msdn.microsoft.com/en-us/library/ee207183.aspx

Brad Cunningham
fonte