协方差
IEnumerable<T>
何时是不同的 IEnumerable<T1>
的子类型?当 T
是 T1
的子类型时。IEnumerable
在其 T
参数中是协变的,这意味着 IEnumerable
的子类型关系与 T
的方向相同。
class Animal { /* ... */ }
class Dog : Animal { /* ... */ }
IEnumerable<Dog> dogs = Enumerable.Empty<Dog>();
IEnumerable<Animal> animals = dogs; // IEnumerable<Dog> is a subtype of IEnumerable<Animal>
// dogs = animals; // Compilation error - IEnumerable<Animal> is not a subtype of IEnumerable<Dog>
具有给定类型参数的协变泛型类型的实例可隐式地转换为具有较少派生类型参数的相同泛型类型。
这种关系成立,因为 IEnumerable
产生 T
s 但不消耗它们。产生 Dog
s 的对象可以像生产 Animal
s 一样使用。
使用 out
关键字声明协变类型参数,因为该参数必须仅用作输出。
interface IEnumerable<out T> { /* ... */ }
声明为协变的类型参数可能不会显示为输入。
interface Bad<out T>
{
void SetT(T t); // type error
}
这是一个完整的例子:
using NUnit.Framework;
namespace ToyStore
{
enum Taste { Bitter, Sweet };
interface IWidget
{
int Weight { get; }
}
interface IFactory<out TWidget>
where TWidget : IWidget
{
TWidget Create();
}
class Toy : IWidget
{
public int Weight { get; set; }
public Taste Taste { get; set; }
}
class ToyFactory : IFactory<Toy>
{
public const int StandardWeight = 100;
public const Taste StandardTaste = Taste.Sweet;
public Toy Create() { return new Toy { Weight = StandardWeight, Taste = StandardTaste }; }
}
[TestFixture]
public class GivenAToyFactory
{
[Test]
public static void WhenUsingToyFactoryToMakeWidgets()
{
var toyFactory = new ToyFactory();
//// Without out keyword, note the verbose explicit cast:
// IFactory<IWidget> rustBeltFactory = (IFactory<IWidget>)toyFactory;
// covariance: concrete being assigned to abstract (shiny and new)
IFactory<IWidget> widgetFactory = toyFactory;
IWidget anotherToy = widgetFactory.Create();
Assert.That(anotherToy.Weight, Is.EqualTo(ToyFactory.StandardWeight)); // abstract contract
Assert.That(((Toy)anotherToy).Taste, Is.EqualTo(ToyFactory.StandardTaste)); // concrete contract
}
}
}