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


188

<out T>和之间有什么区别<T>?例如:

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

public interface IExample<T>
{
    ...
}

1
很好的示例是IObservable <T>和IObserver <T>,它们在mscorlib的系统ns中定义。公共接口IObservable <out T>和公共接口IObserver <in T>。同样,IEnumerator <out T>,IEnumerable <out T>
VivekDev

1
我遇到的最好的解释是:agirlamonggeeks.com/2019/05/29/…。(<in T> <–意味着T只能作为参数传递给方法; <out T> <–意味着T可以仅作为方法结果返回)
Uladzimir Sharyi

Answers:


211

out泛型中的关键字用于表示接口中的类型T是协变的。有关详细信息,请参见协方差和协方差

典型的例子是IEnumerable<out T>。由于IEnumerable<out T>是协变的,因此您可以执行以下操作:

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

如果这不是协变的,则上面的第二行将失败,即使从逻辑上讲它应该可以工作,因为字符串是从对象派生的。前方差通用接口加入到C#和VB.NET(.NET 4的与VS 2010),这是一个编译时错误。

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

IList<T>由于IList<T>具有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<T>无法将其标记为协变。

顺便说一句,还有一个选项in-比较接口之类的东西使用。 IComparer<in T>,例如,以相反的方式工作。因为接口是互变的,所以可以IComparer<Foo>直接将具体用作IComparer<Bar>if Bar是其的子类。FooIComparer<in T>


4
@ColeJohnson因为Image是抽象类;)您可以毫无问题地进行操作new List<object>() { Image.FromFile("test.jpg") };,也可以这样做new List<object>() { new Bitmap("test.jpg") };。您的问题new Image()是不允许的(您也不能这样做var img = new Image();
Reed Copsey 2012年

4
泛型IList<object>是一个奇怪的示例,如果需要,则object不需要泛型。
Jodrell 2013年

5
@ReedCopsey您是否在评论中与自己的答案矛盾?
MarioDS

64

为了轻松记住inand out关键字的用法(也包括协方差和逆方差),我们可以将继承图像包装:

String : Object
Bar : Foo

进出


11
这不是错误的方法吗?Contravariance = in =允许使用较少的派生类型来代替较多的派生类型。/协方差= out =允许使用更多派生类型代替更少派生类型。就个人而言,看着你的图,我读到的是相反的图。
山姆·希尔斯

co u变体(:对我来说
snr

49

考虑,

class Fruit {}

class Banana : Fruit {}

interface ICovariantSkinned<out T> {}

interface ISkinned<T> {}

和功能,

void Peel(ISkinned<Fruit> skinned) { }

void Peel(ICovariantSkinned<Fruit> skinned) { }

接受的函数ICovariantSkinned<Fruit>将能够接受,ICovariantSkinned<Fruit>或者ICovariantSkinned<Bananna>因为ICovariantSkinned<T>是协变接口并且Banana是的类型Fruit

接受的功能ISkinned<Fruit>将只能接受ISkinned<Fruit>


37

out T”表示类型T为“协变”。这就限制T了它只能在通用类,接口或方法的方法中显示为返回(出站)值。这意味着您可以将类型/接口/方法转换为具有的超类型的等效项T
例如,ICovariant<out Dog>可以强制转换为ICovariant<Animal>


13
在我读完此答案之前,我没有意识到只能out强制T返回的强制执行。整个概念现在变得更有意义!
MarioDS 2015年

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.