2013-03-23 64 views
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);如何使泛型類包含一組僅包含其自己的類型或子類型的子集?

我不知道,如果上面的代碼是演示一下我想要做的最明確的方式,所以我會盡量以純解釋英語:

我希望能夠擁有一個類的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>>

有沒有辦法,或者我只是期望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. 
    } 
} 
+0

你嘗試鑄造的孩子爲「ATypeImpl」我會嘗試更多的後來解釋一些? –

+0

一旦構建數據結構,您希望如何與數據結構進行交互? – dtb

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

(擡頭看評論)

這並不意味着它會在你的現實生活中的情況下工作 - 因爲這需要一些「設計重構」與更復雜的結構保持工作(如果可能的話)。

...就快,如果需要

+0

是的,看起來像它,類似於原始代碼和其他答案,但與界面上的'出'使用泛型來解決協方差問題。這對我來說很有意義,但有一點我仍然不確定,那就是它是如何實際執行的。那是因爲ISet 不是協變的,因此你被迫投射到集合中的任何東西? – johansson

+0

我會編輯一些解釋 - 我不確定我是否理解這個問題 - 但讓我試試看 - 首先,如果你拿出'out'看看會發生什麼,那麼最好。或者,如果你在'INode'中設置了'ISet'而不是'IEnumerable'。 W/O'out'(這使得INode協變),它不允許你上傳'INode ' - >'INode '。因爲沒有任何指定它是不允許的(「狗:哺乳動物」沒有多大意義(這取決於你如何使用它)因此,我們需要使'INode <>'的行爲非常像' IEnumerable'(定義類似)爲此,我們需要刪除任何'Add' ... – NSGaga

+0

...接受''作爲'input param'(逆變/ in) - 這是'幸運的是,它也是'IEnumerable' - 所以我們可以通過保持'INode'乾淨並且'只讀'並且枚舉非常多來'欺騙'它,然後'Node'負責保持ISet和添加孩子等等。但是,我們仍然有上傳的權利(請記住,ISet 必須是這樣的(不是「ISet 」),因此,簡而言之 - 你有兩個相反的概念正在進行 - ISet /添加('參數T')和上傳 - 因此我們必須「分割」責任。 – NSGaga

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()); 
+0

沒錯,但是如果我寫IMammalWrapper從IAnimalWrapper繼承,會發生同樣的問題。 基本上,我想限制在包裝中包含的ISet只有定義的類型。 – johansson

相關問題