2013-03-23 2 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);Как создать общий класс, который содержит набор только своего типа или подтипов в качестве детей?

Я не уверен, если приведенный выше код является наиболее ясно способ продемонстрировать то, что я хочу сделать, поэтому я попытаюсь объяснить на простом English:

Я хочу, чтобы у меня был класс, чьи объекты 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#? Черт, я смущаюсь. :)

Edit: Хорошо, так что я почти понял это, без 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

Основная проблема, немного упрощенно, в ковариации (и приведения к базовому типу контрвариации с Исети)

Try это так ...

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

Я отредактирую некоторые объяснения в нем - я не уверен, что я полностью понял вопрос, но позвольте мне попробовать - во-первых, лучше всего, если вы выберете «выход» и посмотрите, что произойдет. Или если вы сделаете 'ISet' вместо' IEnumerable' в 'INode'. W/o 'out' (что делает' INode' covariant), это не позволит вам ускорить 'INode ' -> 'INode '. Потому что, без какого-либо обозначения, это не разрешено (факт, что «Собака: Млекопитающее» не означает многого (это зависит от того, как вы его используете). Поэтому нам нужно заставить 'INode <>' вести себя так же, как ' IEnumerable' (который определен аналогично). Для этого нам нужно изгнать любые 'Add' ... – NSGaga

+0

..., которые принимают' 'как' input param' (контравариантность/'in') - это где' ISet' мешает. К счастью, это также «IEnumerable», поэтому мы можем «обмануть» его, сохранив чистоту «INode» и «только чтение» и перечислив в значительной степени. Затем «Node» берет на себя ответственность за сохранение ISet и добавление детей и т. д. Однако мы все равно получаем право на повышение (имейте в виду, что 'ISet ' должен быть таким (не 'ISet '), что существенно. Итак, короче говоря, у вас есть две противоположные концепции - ISet/добавление ('in' param 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; } 
} 

Затем вам нужно будет изменить способ создания экземпляра коллекции Дети ...

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

Смежные вопросы