【问题标题】:<out T> vs <T> in Generics泛型中的 <out T> 与 <T>
【发布时间】: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/….(

标签: c# generics covariance


【解决方案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; 的工作方式正好相反。如果Bar 是Foo 的子类,您可以直接将具体的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】:

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

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; 是协变接口,而Banana 是Fruit 的类型,

接受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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-09
      • 2020-01-11
      • 1970-01-01
      • 2011-08-13
      • 2011-09-24
      • 1970-01-01
      相关资源
      最近更新 更多