要在 C# 中使用泛型变量,您必须满足以下所有条件:
- 使用 C# 4
- 变化的类型必须是泛型接口或泛型委托
- 类型参数必须标记为“in”(逆变)或“out”(协变)
- 类型参数注释必须产生一个在所有可能的操作中可证明是类型安全的类型
- “源”类型参数和“目标”类型参数之间必须有标识或引用转换。
您的程序满足条件 1 和条件 5,但不满足条件 2、3 或 4。
没有办法得到你想要的方差,因为你想要一些违反条件 4的东西。观察当我们满足除条件 4 之外的所有条件时会发生什么:
// Suppose this declaration were legal:
interface IMyCollection<out T>
{
List<T> Items { get; set; }
}
class Animal {}
class AnimalCollection : IMyCollection<Animal>
{
public List<Animal> { get; set; }
}
class Giraffe : Animal {}
class GiraffeCollection : IMyCollection<Giraffe>
{
public List<Giraffe> { get; set; }
}
static class X
{
public IMyCollection<Animal> GetThing()
{
// IMyCollection is covariant in T, so this is legal.
return new GiraffeCollection();
}
}
class Tiger : Animal {}
...
IMyCollection<Animal> animals = X.GetThing();
// GetThing() actually returns a GiraffeCollection
animals.Items = new List<Animal>() { new Tiger(); }
长颈鹿集合现在包含一个包含老虎的动物列表。
如果要使用方差,则必须是类型安全的。因为编译器无法确定变体注解是类型安全的,所以它拒绝 IMyCollection 的声明。
让我们看看我们如何做到这一点并保证类型安全。问题是 Items 可以通过接口写入。
interface IMyCollection<out T>
{
IEnumerable<T> Items { get; }
}
class Animal {}
class AnimalCollection : IMyCollection<Animal>
{
public IEnumerable<Animal> { get { yield return new Tiger(); } }
}
class Giraffe : Animal {}
class GiraffeCollection : IMyCollection<Giraffe>
{
public IEnumerable<Giraffe> { get { yield return new Giraffe(); } }
}
static class X
{
public IMyCollection<Animal> GetThing()
{
return new GiraffeCollection();
}
}
class Tiger : Animal {}
...
MyCollection<Animal> animals = X.GetThing();
// GetThing() actually returns a GiraffeCollection
foreach(Animal animal in animals.Items) { ... }
// Items yields a giraffe, which is an animal
完美的类型安全。这将是一个合法的 C# 4 程序。
如果您对 C# 4 中协变和逆变的设计细节感兴趣,您可以考虑阅读我关于该主题的十几篇文章。你可以在这里找到它们:
http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/
请注意,这些是按从最近到最近的顺序列出的;从底部开始。
如果您特别对确定接口注释何时有效的规则感兴趣,请参阅这篇文章(我刚刚发现并修复了一些错误,所以我很高兴我们进行了这次对话。)
http://blogs.msdn.com/b/ericlippert/archive/2009/12/03/exact-rules-for-variance-validity.aspx