Creating a shared list of composite common subtypes in C #

I implemented the following hierarchical data structure: Tree <T> → Branch <T> → T.

UPDATE: many people have asked: why don't you use an object instead of <T> (or <dynamic> or whatever)? So I changed my question to indicate my “limitations”. Here we go...

Here is an example of Tree s:

 * ├─Negative │ ├─-2 ├─0 │ ├─0 ├─Positive │ ├─2 │ ├─12 │ ├─2147483647 * ├─Spring │ ├─Mar │ ├─Apr │ ├─May ├─Summer │ ├─Jun │ ├─Jul │ ├─Aug ├─Fall │ ├─Sep │ ├─Oct │ ├─Nov ├─Winter │ ├─Dec │ ├─Jan │ ├─Feb 

Implementation in C #:

 public class Tree<T> { public readonly List<Branch<T>> Branches = new List<Branch<T>>(); } public class Branch<T> { public readonly List<T> Leaves = new List<T>(); public string Name { get; set; } } public class StringLeaf { public StringLeaf(string value) { Label = value; } public string Label { get; private set; } public override string ToString() { return Label; } } public class PositiveIntLeaf { private readonly int _value; public PositiveIntLeaf(int value) { _value = value; } public string Value { get { return _value < 0 ? "-" : _value.ToString(); } } public override string ToString() { return Value; } } public class IntTree : Tree<IntLeaf> { private readonly Branch<IntLeaf> _negatives = new Branch<IntLeaf> { Name = "Negative" }; private readonly Branch<IntLeaf> _zeros = new Branch<IntLeaf> { Name = "0" }; private readonly Branch<IntLeaf> _positives = new Branch<IntLeaf> { Name = "Positive" }; public IntTree() { Branches.AddRange(new []{ _negatives, _zeros, _positives }); } public void Add(int value) { if (value < 0) _negatives.Leaves.Add(new IntLeaf(value)); else if (value > 0) _positives.Leaves.Add(new IntLeaf(value)); else _zeros.Leaves.Add(new IntLeaf(value)); } } 

Assuming I have different trees, I cannot put them on a list :

 IntTreeintTree = new IntTree(); intTree.Add(-2); intTree.Add(2); intTree.Add(0); intTree.Add(12); intTree.Add(int.MaxValue); Tree<StringLeaf> months = new Tree<StringLeaf>{ Branches = { new Branch<StringLeaf> { Name = "Spring", Leaves = { new StringLeaf( "Mar"),new StringLeaf("Apr") ,new StringLeaf("May")} }, new Branch<StringLeaf> { Name = "Summer", Leaves = { new StringLeaf( "Jun"),new StringLeaf("Jul") ,new StringLeaf("Aug")} }, new Branch<StringLeaf> { Name = "Fall", Leaves = { new StringLeaf( "Sep"),new StringLeaf("Oct") ,new StringLeaf("Nov")} }, new Branch<StringLeaf> { Name = "Winter", Leaves = { new StringLeaf( "Dec"),new StringLeaf("Jan") ,new StringLeaf("Feb")} } }}; var list = new [] { intTree, months }; var currentTree = list[0]; // Work with the current tree: var count = currentTree.Branches.Count; Display(currentTree); 

Errors: There is no better type for an implicitly typed array

How to get a list of all these trees?

I would like to emphasize that I just want to put them in a list, possibly iterate through it and access to the current tree and all its branches (for example, to display their names). I don't care if T is an object or an abstract base class! Suppose I just call .ToString() . The specific type is simply important for subtypes of type IntTree .

+7
generics c #
source share
3 answers

You can not. This is a problem known as covariance and contravariance in generics. You cannot stuff giraffes and tigers into the list of animals and hope that everything will be alright, because your collection is a list of animals.

So many articles have been written about this problem that I will not describe it in detail. Just go to MSDN or other articles on Google.

+3
source share

In one general collection, only one type of element will be known . Just as List<int> knows that its element is int , List<T> knows that its element is T

So, if you know that all of your trees will have the same type, but you do not know which , you need to abstract from this type:

 class MyTreeHandler<T> { public List<Tree<T>> myListOfTrees = new List<Tree<T>>(); } 

and "here you go." Of course, this will NOT work when trying to put heterogeneous trees on this list, since T one type of element .

So, since collections of type List know only one type of their element, before you can put heterogeneous objects in one common list / collection, you should find a “common denominator” for all of them.

The simplest common denominator is object , but of course it is. Thus, the most dumb and always working approach:

 List<object> 

and you can put all the Trees there.

But, of course, you didn’t want to hear that either. It just shows you the total value.

What is the common denominator of your trees? As you introduced, no. Of course, they are Trees<T> aka Tree``1 (there should be one return move, but I don’t know how to write it here), but remember that, with very few cases, you cannot use the non-parameterized type Tree<> in c #. And you cannot use Tree<T> without pointing T to something. And after you define it, Tree<T1> and Tree<T2> will be considered as two separate class hierarchies, unless T1 == T2 or if you do not use some in/out dispersion specifiers (e.g. IEnumerable<out> therefore IEnumerable<Kangaroo> matters >). I assume that you want mixed variance, therefore not an option. But if you want your tree to be only input or output, use the co / contravariance specifiers immediately!

So, let's leave generics and their deviations. To provide a C # comprehensible language / compiler, you need to enter something more .. basic.

A common abstract base other than object , some common interface . No matter what you think is the least dirty.

When I really had to mix and store generic files of a different type, I usually introduced a simple dual interface:

 public interface IMyThing //Typeless { Type TypeParam {get;} object Operation(object a); object Property {get;set;} } public interface IMyThing<T> //TypeD { T Operation(T a); T Property {get;set;} } public class MyThing<T> : IMyThing<T>, IMyThing { public T Operation(T a) { .. } public T Property {get;set;} Type IMyThing.TypeParam {get{return typeof(T);}} object IMyThing.Operation(object a){return this.Operation((T)a);} object IMyThing.Property {get{return this.Property;}set{this.Property=(T)value;}} } 

Now I can use every thing for general IMyThing and store them as a mixed, impersonal List<IMyThing> . Thanks to the explicit implementation of the interface, I will not see any “untyped” members unless I drop them, so other T-aware will look as usual.

But, when I have to mix types, I can now apply them to a common impersonal interface, and thanks to the interface I will still have access to operations / properties. Assuming I somehow guarantee that the arguments will depend on the correct T. But I can always check the TypeParam and process it manually.

So pure trouble. Be that as it may, note that IEnumerable<T> and IEnumerable do the same!

Screaming for dynamic , but in fact, without dynamic (i.e.. .Net 3.5 platform without DLR?), There are few opportunities for other solutions.

+3
source share

An alternative would be to use the dynamic type. Try the following:

 static void Main(string[] args) { Tree<dynamic> intTree = CreateTree<dynamic> (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 15); Tree<dynamic> charTree = CreateTree<dynamic> ('1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'C', 'E'); Tree<dynamic> months = CreateTree<dynamic> ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"); IEnumerable<Tree<object>> list = new List<Tree<object>> { intTree, charTree, months }; } private static Tree<T> CreateTree<T>(params T[] values) { var t = new Tree<T>(); var b = new Branch<T>(); // some branching logic for (int i = 0; i < values.Length; i++) { if (i % 4 == 3) { b.Leaves.Add(values[i]); t.Branches.Add(b); b = new Branch<T>(); } else b.Leaves.Add(values[i]); } if (b.Leaves.Count != 0) t.Branches.Add(b); return t; } 

You can also create an extension method for your purpose:

 public static Tree<dynamic> MakeDynamic<T>(this Tree<T> inputTree) { var newTree = new Tree<dynamic>(); for (int i = 0; i < inputTree.Branches.Count; i++) { var newBranch=new Branch<dynamic>(); newTree.Branches.Add(newBranch); for (int j = 0; j < inputTree.Branches[i].Leaves.Count; j++) { dynamic d = inputTree.Branches[i].Leaves[j]; inputTree.Branches[i].Leaves[j] = d; } } return newTree; } 

In this last case, you can save the original types and use this example:

 IEnumerable<Tree<object>> list = new List<Tree<object>> { intTree.MakeDynamic(), charTree.MakeDynamic(), months.MakeDynamic() }; 
-one
source share

All Articles