C#为什么我不能将键入子类的hashset添加到键入其超类的hashset中

时间:2013-05-31 12:36:29

标签: c# generics collections hashset

为什么我在以下代码中遇到编译错误(参见注释行)?

    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&lt; Expenses.Tests.TestDb.SetTest.Cat&gt;'转换到'System.Collections.Generic.HashSet&lt; Expenses.Tests.TestDb.SetTest.Animal&gt;'
  • 错误1'System.Collections.Generic.HashSet&lt; System.Collections.Generic.HashSet&lt; Expenses.Tests.TestDb.SetTest.Animal&gt;&gt; .Add(System.Collections.Generic.HashSet)的最佳重载方法匹配)'有一些无效的论点

我的问题是:为什么我不能将“猫”添加到“setWithSets”?

4 个答案:

答案 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来自AnimalHashSet<Cat>也不是HashSet<Animal>派生的。 (HashSet<Anything>的唯一基类是object类。)

要获得所需的行为,HashSet<T>泛型类型必须在其类型参数T中为 协变 。但事实并非如此,原因有两个:

  1. 在C#中,只有通用接口和通用委托类型可以是共变或逆变。 HashSet<>是一个班级。
  2. 您不仅可以从HashSet<>阅读,还可以 添加 (并执行其他操作)。因此,协方差在逻辑上是不可能的。或者,我们可以将HashSet<Cat>视为HashSet<Animal>,然后为其添加Dog。但是一组猫不允许狗。
  3. 如果您将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;