Using generics in parent-child relationships

I have an abstract BaseItem class declared as follows:

public abstract class BaseItem { public BaseItem Parent { get; protected set; } public List<BaseItem> Children = new List<BaseItem>(); public abstract string Function1(); } 

Basically, I am trying to implement a project in which each element has a parent element that will have one particular type and child elements that will have a different type.

For example, ItemA will have children for all ItemB types. Then ItemB will have the parent type ItemA and all types of ItemC. ItemC will have a parent ItemB and child items of type ItemD.

I thought it would be tidier to use this with generics to avoid unnecessary throws, as I know what type the parent and child will be for each of my inherited classes. So I came up with something like this:

 public abstract class AbstractBase { public abstract string Function1(); } public abstract class BaseItem<T1, T2> : AbstractBase where T1 : AbstractBase where T2 : AbstractBase { public T1 Parent { get; protected set; } public List<T2> Children = new List<T2>(); } public class ItemA : BaseItem<ItemA, ItemB> { } public class ItemB : BaseItem<ItemA, ItemC> { } public class ItemC : BaseItem<ItemB, ItemD> { } public class ItemD : BaseItem<ItemC, ItemD> { } 

So, two things. 1. Is it a good design? Is there an easier / better way to do this? I don't like using a second abstract base class to use generics. 2. If I hold it, what's the best way to handle the ends? (for example, ItemA does not have a parent in my actual problem domain, but you need a parent to compile it. ItemD does not have children, but I need to give something)

+7
generics c #
source share
5 answers

If I understand correctly, you say that ItemA never has a parent, and ItemD never has children, right? Honestly, I would just declare separate classes with their parent / child properties of the correct type:

 public abstract class AbstractBase { public abstract string Function1(); } public class ItemA : AbstractBase { public List<ItemB> Children = new List<ItemB>(); } public class ItemB : AbstractBase { public ItemA Parent { get; protected set; } public List<ItemC> Children = new List<ItemC>(); } public class ItemC : AbstractBase { public ItemB Parent { get; protected set; } public List<ItemD> Children = new List<ItemD>(); } public class ItemD : AbstractBase { public ItemC Parent { get; protected set; } } 

The only thing you repeat here is the Parent property and the Children property / field. All other common functions that you can implement in AbstractBase . (Unless, of course, this functionality does not need access to parents / children - but then you will return to the square even in your decision.)

+1
source share

I would do it like this:

 public interface IChildOf<T> { T Parent { get; set; } } public interface IParent<T> { List<T> Children { get; set; } } //This class handles all of the cases that have both parent and children public abstract class BaseItem<T1, T2> : IParent<T1>, IChildOf<T2> { public List<T1> Children { get; set; } public T2 Parent { get; set; } } //This class handles the top level parent public class ItemA : IParent<ItemB> { public List<ItemB> Children { get; set; } } public class ItemB : BaseItem<ItemC, ItemA> { } public class ItemC : BaseItem<ItemD, ItemB> { } //.... as many intermediates as you like. //This class handles the bottom level items with no children public class ItemD : IChildOf<ItemC> { public ItemC Parent { get; set; } } 
+1
source share

You can use two common interfaces: ChildOf<T> and IList<T> . This will allow you to handle end cases. Unfortunately, since .NET does not have multiple inheritance, you cannot share it.

Alternatively, you could use an abstract class and marker type ( System.Void would be ideal, but I don't think it would compile) for the final cases, and check it with the Parent / Children property and exception.

0
source share

This does not seem like a bad idea, although, frankly, I will be tempted to keep the original solution and put up with casting, as it adds a bit of complexity so as not to bring much benefit. (Although I have to admit that I try to replace casting with generics whenever I can).

If you want to keep this, a few suggestions:

  • have AbstractBase as an interface
  • do something else for purposes (for example, two additional BaseItems that take the parent and child generic types).

Obviously, this second point only adds to the complexity, and this is another reason why I am not sure what it costs.

0
source share

I don’t see a problem with this because you have a specific hierarchy that requires top-level items to be of type ItemA , 2nd-level items to be of type ItemB , etc.

To access the root and leaf nodes, I would probably define separate classes that are also derived from AbstractBase :

 public abstract class RootItem<T2> : AbstractBase where T2 : AbstractBase { public List<T2> Children = new List<T2>(); } public abstract class LeafItem<T1> : AbstractBase where T1 : AbstractBase { public T1 Parent { get; protected set; } } public class ItemA : RootItem<ItemB> { } public class ItemB : BaseItem<ItemA, ItemC> { } public class ItemC : BaseItem<ItemB, ItemD> { } public class ItemD : LeafItem<ItemC> { } 
0
source share

All Articles