協方差
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
}
}
}