泛型中的 <out T> 与 <T>

Posted

技术标签:

【中文标题】泛型中的 <out T> 与 <T>【英文标题】:<out T> vs <T> in Generics 【发布时间】:2012-06-08 23:05:42 【问题描述】:

&lt;out T&gt;&lt;T&gt; 有什么区别?例如:

public interface IExample<out T>

    ...

对比

public interface IExample<T>

    ...

【问题讨论】:

很好的例子是 IObservable 和 IObserver,在 mscorlib 的系统 ns 中定义。公共接口 IObservable 和公共接口 IObserver。同样,IEnumerator、IEnumerable 我遇到的最好的解释:agirlamonggeeks.com/2019/05/29/….( 【参考方案1】:

泛型中的out关键字用于表示接口中的类型T是协变的。详情请见Covariance and contravariance。

经典的例子是IEnumerable&lt;out T&gt;。由于IEnumerable&lt;out T&gt; 是协变的,因此您可以执行以下操作:

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

如果这不是协变的,上面的第二行将失败,即使在逻辑上它应该工作,因为 string 派生自 object。在将 variance in generic interfaces 添加到 C# 和 VB.NET(在 .NET 4 和 VS 2010 中)之前,这是一个编译时错误。

在 .NET 4 之后,IEnumerable&lt;T&gt; 被标记为协变,变成了IEnumerable&lt;out T&gt;。由于IEnumerable&lt;out T&gt; 仅使用其中的元素,而从不添加/更改它们,因此将可枚举的字符串集合视为可枚举的对象集合是安全的,这意味着它是协变的。 p>

这不适用于IList&lt;T&gt; 这样的类型,因为IList&lt;T&gt; 有一个Add 方法。假设这是允许的:

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

然后你可以调用:

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

这当然会失败 - 所以IList&lt;T&gt; 不能被标记为协变。

顺便说一句,in 也有一个选项 - 用于比较接口之类的东西。例如,IComparer&lt;in T&gt; 的工作方式正好相反。如果BarFoo 的子类,您可以直接将具体的IComparer&lt;Foo&gt; 用作IComparer&lt;Bar&gt;,因为IComparer&lt;in T&gt; 接口是逆变的

【讨论】:

@ColeJohnson 因为Image 是一个抽象类;)您可以毫无问题地使用new List&lt;object&gt;() Image.FromFile("test.jpg") ;,或者您也可以使用new List&lt;object&gt;() new Bitmap("test.jpg") ;。你的问题是new Image()是不允许的(你也不能var img = new Image(); 泛型IList&lt;object&gt; 是一个奇怪的例子,如果你想要objects,你就不需要泛型。 @ReedCopsey 您在评论中的回答不是自相矛盾吗?【参考方案2】:

为了容易记住 inout 关键字的用法(也是协变和逆变),我们可以将继承想象成包装:

String : Object
Bar : Foo

【讨论】:

这不是错误的方式吗?逆变 = in = 允许使用较少派生类型代替更多派生类型。 / Covariance = out = 允许使用更多派生类型代替更少派生类型。就个人而言,看着你的图表,我读到的正好相反。 co u 变体(:对我来说【参考方案3】:

考虑,

class Fruit 

class Banana : Fruit 

interface ICovariantSkinned<out T> 

interface ISkinned<T> 

以及功能,

void Peel(ISkinned<Fruit> skinned)  

void Peel(ICovariantSkinned<Fruit> skinned)  

接受ICovariantSkinned&lt;Fruit&gt; 的函数将能够接受ICovariantSkinned&lt;Fruit&gt;ICovariantSkinned&lt;Banana&gt;,因为ICovariantSkinned&lt;T&gt; 是协变接口,而BananaFruit 的类型,

接受ISkinned&lt;Fruit&gt; 的函数将只能接受ISkinned&lt;Fruit&gt;

【讨论】:

【参考方案4】:

out T”表示T 类型是“协变的”。这限制了T 在泛型类、接口或方法的方法中仅作为返回(出站)值出现。这意味着您可以将类型/接口/方法转换为具有T 超类型的等价物。 例如。 ICovariant&lt;out Dog&gt; 可以转换为 ICovariant&lt;Animal&gt;

【讨论】:

我没有意识到 out 强制 T 只能返回,直到我阅读了这个答案。整个概念现在更有意义了!【参考方案5】:

从您发布的链接中......

对于泛型类型参数,out 关键字指定类型 参数是协变的

编辑: 同样,来自您发布的链接

有关详细信息,请参阅协变和逆变(C# 和 Visual Basic)。 http://msdn.microsoft.com/en-us/library/ee207183.aspx

【讨论】:

以上是关于泛型中的 <out T> 与 <T>的主要内容,如果未能解决你的问题,请参考以下文章

泛型中? super T和? extends T的区别

java 泛型中的T和?

Java泛型中<?>和<T>的区别

java泛型中的super和extend

Java集合与泛型中的几个陷阱,你掉进了几个?

泛型中extends 和super 的区别