为什么我在以下代码中遇到编译错误(参见注释行)?
public void Test()
{
HashSet<HashSet<Animal>> setWithSets = new HashSet<HashSet<Animal>>();
HashSet<Cat> cats = new HashSet<Cat>();
setWithSets.Add(cats); // Compile error
}
private class Animal { }
private class Cat : Animal { }
VS2012给了我两个错误,第一个是重要错误:
我的问题是:为什么我不能将“猫”添加到“setWithSets”?
答案 0 :(得分:10)
为了更好地理解为什么不允许这样做,请考虑以下程序。
编译器可以接受行setOfSets.First().Add(new Dog());
,因为动物集合肯定可以拥有Dog
的实例。问题是集合中的第一个动物集合是Cat
个实例的集合,Dog
没有扩展Cat
。
class Animal { }
class Cat : Animal { }
class Dog : Animal { }
class Program {
static void Main(string[] args) {
// This is a collection of collections of animals.
HashSet<HashSet<Animal>> setOfSets = new HashSet<HashSet<Animal>>();
// Here, we add a collection of cats to that collection.
HashSet<Cat> cats = new HashSet<Cat>();
setOfSets.Add(cats);
// And here, we add a dog to the collection of cats. Sorry, kitty!
setOfSets.First().Add(new Dog());
}
}
答案 1 :(得分:5)
即使Cat
来自Animal
,HashSet<Cat>
也不是HashSet<Animal>
派生的。 (HashSet<Anything>
的唯一基类是object
类。)
要获得所需的行为,HashSet<T>
泛型类型必须在其类型参数T
中为 协变 。但事实并非如此,原因有两个:
HashSet<>
是一个班级。HashSet<>
阅读,还可以 添加 (并执行其他操作)。因此,协方差在逻辑上是不可能的。或者,我们可以将HashSet<Cat>
视为HashSet<Animal>
,然后为其添加Dog
。但是一组猫不允许狗。如果您将HashSet<T>
更改为例如IReadOnlyCollection<T>
(请参阅.NET 4.5 documentation: IReadOnlyCollection<out T>
Interface),那么事情会起作用,因为后一种类型(1)是一个接口,(2)只允许阅读,并且(3)因此在这个类型的作者决定适用的T
中预先标记了“我是协变的”。
答案 2 :(得分:5)
您收到编译器错误,因为HashSet的类型构造函数是不变。
有关术语“不变量”的解释,请查看Covariance and contravariance
答案 3 :(得分:2)
因为HashSet<Cat>
不是从HashSet<Animal>
派生的,这是您想要做的事情所必需的。
您可以做的是向Cat
添加HashSet<Animal>
,因为Cat
来自Animal
无法做的是将HashSet<Cat>
添加到HashSet<HashSet<Animal>>
你可能认为你可以使用协方差,这可以让你这样做:
IEnumerable<Cat> cats = new List<Cat>();
IEnumerable<Animal> animals = cats;
这是有效的,因为这是IEnumerable的接口声明:
public interface IEnumerable<out T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
}
注意'out T'?这是协方差。它基本上允许您在通用类型的类上具有类似继承的行为。请注意,您只能在接口上声明协方差。现在让我们看看ISet,即HashSet实现的接口:
public interface ISet<T> : ICollection<T>, IEnumerable<T>, IEnumerable
{
...
}
如您所见,没有'out'关键字。这意味着你不能这样做:
ISet<Cat> cats = new HashSet<Cat>();
ISet<Animal> animals = cats;