4
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);

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

我希望能够拥有一个类,其子对象属于ISet相同的泛型类型。因此,如果我也有var child = new AnimalWrapper<Reptile>();它,在编译时会foo.Children.Add(child);因为Reptileis not 也不会继承自Mammal. 但是,显然,即使它是派生的,如上所示,它也不起作用。

最终,能够说ISet<AnimalWrapper<Animal>> baz = new HashSet<AnimalWrapper<Animal>>();然后将 a 添加new AnimalWrapper<Mammal>()到该集合以及new AnimalWrapper<Reptile>()同一集合中会很好。如上所述,他们的孩子将拥有一个属于自己类型的财产ChildrenISet<AnimalWrapper<T>>

有什么办法,还是我对 C# 期望过高?哎呀,我自己都糊涂了。:)

编辑:好的,所以我几乎想通了,没有AnimalWrapper,但有一个基本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.
    }
}
4

2 回答 2

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);
    }
}

(查看评论)

这并不意味着它可以在您的实际案例中工作 - 因为这需要一些“设计重构”以使其与更复杂的结构一起工作(如果可能的话)。

...快点,如果需要,我稍后会尝试解释更多

于 2013-03-24T14:25:07.797 回答
1

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

我看到你可以解决这个问题的一种可能方法是如果你要实现一个接口。

interface IAnimalWrapper { }

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

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

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>单独提供类型......

var foo = new Mammal();
foo.Children = new HashSet<AnimalWithChildren>();
foo.Children.Add(new Mammal());
foo.Children.Add(new Dog());
foo.Children.Add(new Reptile());
于 2013-03-23T22:48:00.637 回答