9

为什么我在以下代码中出现编译错误(请参阅注释行)?

    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 给了我两个错误,第一个是重要的:

  • 错误 2 参数 1:无法从 'System.Collections.Generic.HashSet<Expenses.Tests.TestDb.SetTest.Cat>' 转换为 'System.Collections.Generic.HashSet<Expenses.Tests.TestDb.SetTest.Animal>'
  • 错误 1 ​​'System.Collections.Generic.HashSet<System.Collections.Generic.HashSet<Expenses.Tests.TestDb.SetTest.Animal>>.Add(System.Collections.Generic.HashSet)' 的最佳重载方法匹配有一些无效参数

我的问题是:为什么我不能在“setWithSets”中添加“cats”?

4

4 回答 4

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());
    }
}
于 2013-05-31T12:49:49.000 回答
7

就算是Cat源于Animal,也不是真的HashSet<Cat>源于HashSet<Animal>。(唯一的基类HashSet<Anything>object类。)

要获得您想要的行为,HashSet<T>泛型类型需要在其类型参数中是协变T的。但事实并非如此,原因有二:

  1. 在 C# 中,只有泛型接口和泛型委托类型可以是协变或逆变的。HashSet<>是一类。
  2. 您不仅可以从 a 中读取HashSet<>,还可以添加到它(并做其他事情)。因此协方差在逻辑上是不可能的。否则可以将 aHashSet<Cat>视为 a HashSet<Animal>,然后将 a 添加Dog到其中。但是一组猫不允许狗。

例如,如果您更改HashSet<T>IReadOnlyCollection<T>(请参阅 .NET 4.5文档:IReadOnlyCollection<out T>Interface),事情会起作用,因为后一种类型 (1) 是一个接口,(2) 只允许读取,并且 (3) 因此预设了一个标记“我是T类型的作者决定应用的“协变”。

于 2013-05-31T12:44:59.223 回答
5

你会得到一个编译器错误,因为 HashSet 的类型构造函数是invariant

有关术语不变量的解释,请查看协方差和逆变

于 2013-05-31T12:45:48.130 回答
2

因为HashSet<Cat>不派生自HashSet<Animal>,这是您想做的事情所必需的。

可以做的是将 a 添加Cat到 a HashSet<Animal>,因为Cat派生自Animal What you can't do is add a HashSet<Cat>to aHashSet<HashSet<Animal>>

你可能认为你可以使用协方差,它允许你这样做:

IEnumerable<Cat> cats = new List<Cat>();
IEnumerable<Animal> animals = cats;

这是可行的,因为这是 IEnumerable 的接口声明:

public interface IEnumerable<out T> : IEnumerable
{
    IEnumerator<T> GetEnumerator();
}

注意到'out T'了吗?这就是协方差。它基本上允许您在泛型类型的类上具有类似继承的行为。请注意,您只能在接口上声明协方差。现在让我们看一下 HashSet 实现的接口 ISet:

public interface ISet<T> : ICollection<T>, IEnumerable<T>, IEnumerable
{
    ...
}

如您所见,没有“out”关键字。这意味着你不能这样做:

ISet<Cat> cats = new HashSet<Cat>();
ISet<Animal> animals = cats;
于 2013-05-31T12:46:18.067 回答