<out T>
和 <T>
有什么区别?例如:
public interface IExample<out T>
{
...
}
与
public interface IExample<T>
{
...
}
泛型中的
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>
接口是 逆变。
为了轻松记住
in
和 out
关键字(还有协变和逆变)的用法,我们可以将继承想象为包装:
String : Object
Bar : Foo
考虑一下,
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>
。
“
out T
”表示类型T
是“协变”。这限制了 T
仅作为泛型类、接口或方法的方法中的返回(出站)值出现。这意味着您可以将类型/接口/方法转换为具有超类型 T
的等价物。ICovariant<out Dog>
可以投射到 ICovariant<Animal>
。
从您发布的链接......
对于泛型类型参数, out 关键字指定类型 参数是协变的。
编辑: 再次,从您发布的链接
有关详细信息,请参阅协方差和逆变(C# 和 Visual Basic)。 http://msdn.microsoft.com/en-us/library/ee207183.aspx
我发现的最简单的解释在这篇文章中描述为
界面
<– 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<in 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!)ISomeName<out T>
//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());
所以基本上:
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
}
}