abstract class Animal { }
class Mammal : Animal { }
class Dog : Mammal { }
class Reptile : Animal { }
class AnimalWrapper where T : Animal
{
public
The main problem is, a bit oversimplified, in covariance upcasting (and contravariance with the ISet)
Try it this way...
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);
}
}
(look up the comments)
This doesn't mean it'd work in your real-life case - as this requires some 'design refactoring' to keep it working with a more complex structure (if possible).
...just fast, I'll try to explain some more later if needed
This happens because when you instantiate an instance of AnimalWrapper<T>
using the generic type argument Mammal
, the Children
member will be of type ISet<AnimalWrapper<Mammal>>
and not of type ISet<AnimalWrapper<Dog>>
. Hence the reason you can't add an instance of AnimalWrapper<Dog>
to the generic collection.
One possible way I see you could address this might be if you were to implement an interface.
interface IAnimalWrapper { }
class AnimalWrapper<T> : IAnimalWrapper where T : Animal
{
public ISet<IAnimalWrapper> Children { get; set; }
}
Then you will need to change the way you instantiate the Children collection...
foo.Children = new HashSet<IAnimalWrapper>();
Now you can add to the different types of children...
foo.Children.Add(new AnimalWrapper<Mammal>());
foo.Children.Add(new AnimalWrapper<Dog>());
foo.Children.Add(new AnimalWrapper<Reptile>());
So that will get it to compile but I am still curious as to why you really need the generic class (AnimalWrapper<T>
). I imagine there could be reasons for it but maybe just doing away with that type would simplify things (depending on the larger context)...
abstract class AnimalWithChildren
{
public ISet<AnimalWithChildren> Children { get; set; }
}
class Mammal : AnimalWithChildren { }
class Dog : Mammal { }
class Reptile : AnimalWithChildren { }
In other words, just rely ISet<T>
alone to provide the type...
var foo = new Mammal();
foo.Children = new HashSet<AnimalWithChildren>();
foo.Children.Add(new Mammal());
foo.Children.Add(new Dog());
foo.Children.Add(new Reptile());