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

问题描述 投票:0回答:8

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

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

public interface IExample<T>
{
    ...
}
c# generics covariance
8个回答
273
投票

泛型中的

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

经典的例子是

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

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

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

.NET 4 之后,

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

这不适用于像

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>
的子类,则可以直接使用具体
Bar
作为
Foo
,因为
IComparer<in T>
接口是 逆变


81
投票

为了轻松记住

in
out
关键字(还有协变和逆变)的用法,我们可以将继承想象为包装:

String : Object
Bar : Foo

in/out


65
投票

考虑一下,

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<Banana>
,因为
ICovariantSkinned<T>
是协变接口,而
Banana
Fruit

的类型。

接受

ISkinned<Fruit>
的函数将只能接受
ISkinned<Fruit>


58
投票

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


8
投票

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

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

编辑: 再次,从您发布的链接

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


3
投票

我认为 VS2022 的这个屏幕截图非常具有描述性 - 它说明了这对泛型带来了什么样的限制:


2
投票

我发现的最简单的解释在这篇文章中描述为

界面

ISomeName<in T>
<– means that T can be only passed as a parameter to a method (it enters inteface’s methods, so it goes inside, maybe that’s why we use here keyword ‘in’… Hmm, no, that’s just a coincidence ?)

界面

ISomeName<out T>
<– means that T can be only returned as method results (it is what we receive from a method so it goes out of it – wow, again sounds legit!)


0
投票
协变输出
//I need a fruit farm(base class) so i can produce fruits(base type)
//But what I have is an apple farm(derived class) that produce apples (derive type)
IProducer<Apple> appleFarm = new AppleProducer(); 
//thats ok, I'll just mark it as covariant(out) since apple is also a fruit
IProducer<Fruit> fruitFarm = appleFarm; 

Apple apple = appleFarm.Produce();
//I can produce fruits since apple is a fruit
Fruit fruit = apple; 
逆变于
//I need an apple bakery(derive class) so i can use my apples(derive type)
//But what I have is a fruit bakery(base class) that uses fruits(base type)
IConsumer<Fruit> fruitBakery = new FruitConsumer();
//thats ok, I'll just mark it as contravariant(in) since a fruit bakery can also consume apples
IConsumer<Apple> appleBakery = fruitBakery;

//can consume apples and any fruits
appleBakery.Consume(new Apple());
fruitBakery.Consume(new Fruit());
fruitBakery.Consume(new Apple());

所以基本上:

  • 如果将接口泛型标记为协变,它必须能够out放入泛型类型,并且它允许您将泛型类型的派生类型转换为其基类型。
IProducer<Apple> appleFarm = new AppleProducer(); 
IProducer<Fruit> fruitFarm = appleFarm; 

Apple apple = appleFarm.Produce();
Fruit fruit = apple; 
  • 如果将接口泛型标记为逆变,它必须能够输入放入泛型类型,并且它允许您将泛型类型的派生类型转换为其基类型。
IConsumer<Fruit> fruitBakery = new FruitConsumer();
IConsumer<Apple> appleBakery = fruitBakery;

fruitBakery.Consume(new Apple());
fruitBakery.Consume(new Fruit());
appleBakery.Consume(new Apple());

要了解原因,请查阅里氏替换原理。

完整代码:

public interface IProducer<out T>
{
    T Produce();
}
 
public class Fruit { }
 
public class Apple : Fruit { }
 
public class FruitProducer : IProducer<Fruit>
{
    public Fruit Produce() => new Fruit();
}
 
public class AppleProducer : IProducer<Apple>
{
    public Apple Produce() => new Apple();
}
 
public interface IConsumer<in T>
{
    void Consume(T item);
}
 
public class FruitConsumer : IConsumer<Fruit>
{
    public void Consume(Fruit item)
    {
        // Consume the fruit
    }
}
 
public class AppleConsumer : IConsumer<Apple>
{
    public void Consume(Apple item)
    {
        // Consume the apple
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.