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

上記のコードが私がやりたいことを示す最も明確な方法であるかどうかわからないので、平易な英語で説明しようとします:

ISetChildren オブジェクトが同じジェネリック型のクラスを持つ機能が必要です。したがって、私もそれを持っていた場合var child = new AnimalWrapper<Reptile>();、コンパイル時に、is ではなく、 から継承しないfoo.Children.Add(child);ため、実行に失敗します。しかし、明らかに、派生したとしても、上記のように機能しません。ReptileMammal

最終的には、そのセットに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>したがって、のインスタンスを汎用コレクションに追加できない理由。

これに対処できると私が思う1つの可能な方法は、インターフェースを実装する場合です。

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 に答える