如何创建包含一组只有自己的类型或子类型为子类的泛型类?

时间:2013-03-23 18:31:58

标签: c# covariance contravariance

abstract class Animal { }

class Mammal : Animal { }

class Dog : Mammal { }

class Reptile : Animal { }

class AnimalWrapper<T> where T : Animal
{
    public ISet<AnimalWrapper<T>> Children { get; set; }
}

class Program
{
    public static void Main(string[] args)
    {
        var foo = new AnimalWrapper<Mammal>();
        foo.Children = new HashSet<AnimalWrapper<Mammal>>();

        var child = new AnimalWrapper<Dog>();
        foo.Children.Add(child);
    }
}

由于foo.Children.Add(child);

,这显然无法编译

我不确定上面的代码是否是用于展示我想要做的最 clear 的方式,所以我将尝试用简单的英语解释:

我希望能够拥有一个类,其Children对象位于相同泛型类型的ISet中。因此,如果我也有var child = new AnimalWrapper<Reptile>();,那么在编译时它将无法foo.Children.Add(child);,因为Reptile不是Mammal并且不从ISet<AnimalWrapper<Animal>> baz = new HashSet<AnimalWrapper<Animal>>();继承。然而,显然,即使它是派生的,如上所示,它也不起作用。

最终,能够说new AnimalWrapper<Mammal>()然后将new AnimalWrapper<Reptile>()添加到该集合并将Children添加到同一集合中会很高兴。如上所述,他们的孩子将拥有一个属ISet<AnimalWrapper<T>>属性AnimalWrapper,在某种程度上它属于自己的类型。

有没有办法,或者我只是期待C#过多?哎我自己很困惑。 :)

编辑:好的,所以我差不多想出来了,没有IAnimal,但是有一个基础interface IAnimal { } abstract class Animal<T> : IAnimal where T : Animal<T> { public ISet<T> Children { get; set; } } class Mammal : Animal<Mammal> { } class Dog : Mammal { } class Reptile : Animal<Reptile> { } class Frog : Reptile { } class Program { public static void Main(string[] args) { var animals = new HashSet<IAnimal>(); // any animal can be in this var mammal = new Mammal(); animals.Add(mammal); mammal.Children = new HashSet<Mammal>(); var dog = new Dog(); mammal.Children.Add(dog); // ok! a dog is a mammal dog.Children = new HashSet<Dog>(); // in theory, OK, but compile time error // because Dog : Mammal, and Mammal defines Animal<Mammal>, therefore Dog's // Children is actually ISet<Mammal>, rather than ISet<Dog> (which is what // I want, recursively apply the T in Animal. Mammal mammal2 = new Mammal(); dog.Children.Add(mammal2); // should be verboten, but is allowed for the // same reason above. } } 界面,它几乎可以工作:

{{1}}

2 个答案:

答案 0 :(得分:2)

主要问题是,协方差向上翻译(以及与ISet的逆转)有点过于简单了

以这种方式试试......

abstract class Animal { }
class Mammal : Animal { }
class Dog : Mammal { }
class Reptile : Animal { }

interface INode<out T> where T : Animal
{
    T MySelf { get; }
    IEnumerable<INode<T>> Children { get; }
}

class Node<T> : INode<T>
    where T : Animal
{
    public Node() { this.Children = new HashSet<INode<T>>(); }
    public T MySelf { get; set; }
    public ISet<INode<T>> Children { get; set; }
    IEnumerable<INode<T>> INode<T>.Children { get { return this.Children; } }
}

class Program
{
    static void Main(string[] args)
    {
        // this is a 'typical' setup - to test compiler 'denial' for the Reptile type...

        Node<Mammal> tree = new Node<Mammal>();
        tree.MySelf = new Mammal();

        var node1 = new Node<Mammal>();
        tree.Children.Add(node1);

        var node2 = new Node<Dog>();
        tree.Children.Add(node2);

        var node3 = new Node<Reptile>();
        // tree.Children.Add(node3); // this fails to compile


        // ...and similar just more 'open' - if you 'collect' animals, all are welcome

        Node<Animal> animals = new Node<Animal>();
        animals.MySelf = new Mammal();

        INode<Mammal> mamals = new Node<Mammal>();
        animals.Children.Add(mamals);

        var dogs = new Node<Dog>();
        animals.Children.Add(dogs);

        INode<Animal> reptiles = new Node<Reptile>();
        animals.Children.Add(reptiles);
    }
}

(查看评论)

这并不意味着它在你的现实生活中起作用 - 因为这需要一些“设计重构”来保持它与更复杂的结构(如果可能)一起工作。

......很快,如果需要,我会稍后再解释一下

答案 1 :(得分:1)

这是因为当您使用泛型类型参数AnimalWrapper<T>实例化Mammal实例时,Children成员将是ISet<AnimalWrapper<Mammal>>类型而不是类型{{ 1}}。因此,您无法将ISet<AnimalWrapper<Dog>>的实例添加到泛型集合中。

我认为你可以解决这个问题的一种可能方法就是实现一个接口。

AnimalWrapper<Dog>

然后,您需要更改实例化Children集合的方式......

interface IAnimalWrapper { }

class AnimalWrapper<T> : IAnimalWrapper where T : Animal
{
    public ISet<IAnimalWrapper> Children { get; set; }
}

现在您可以添加到不同类型的孩子......

foo.Children = new HashSet<IAnimalWrapper>();

所以这将使它得到编译,但我仍然很好奇为什么你真的需要泛型类(foo.Children.Add(new AnimalWrapper<Mammal>()); foo.Children.Add(new AnimalWrapper<Dog>()); foo.Children.Add(new AnimalWrapper<Reptile>()); )。我想可能有理由,但也许只是取消这种类型会简化事情(取决于更大的背景)......

AnimalWrapper<T>

换句话说,只需依靠abstract class AnimalWithChildren { public ISet<AnimalWithChildren> Children { get; set; } } class Mammal : AnimalWithChildren { } class Dog : Mammal { } class Reptile : AnimalWithChildren { } 来提供类型......

ISet<T>