JustOneQuestion JustOneQuestion - 2 months ago 14
C# Question

Inherit in generic classes C#

My brain is gonna to explode. :) So I would like to get help from you.
Please, think about my question like about just programmer puzzle. (Actually. perhaps it is very easy question for you, but not for me.)

It is needed to create array of objects. For example List where T is class. (I will describe Class T below). Also it is needed create “container” that will contain this array and some methods for work with this array. For example Add(), Remove(int IndexToRemove).
Class T must have field "Container", this way each elements of our array would be able to know where is it contained and has access its container's fields and methods. Notice, that in this case Class T should have type parameter. Indeed, it is not known beforehand which container's type is used.
Let us denote this class container as A and class element (class T) as AUnit.

Code:

class Program
{
static void Main(string[] args)
{
A a = new A();
a.Add();
a.Units[0].SomeField +=100;
Console.ReadKey();
}
}


class A
{
public List<AUnit> Units;

public A()//ctor
{
Units = new List<AUnit>();
}
public void Add()
{
this.Units.Add(new AUnit(this));
}
}
class AUnit
{
public int SomeField;
public A Container;
public string Name { get; private set; }
public AUnit(A container)
{
this.SomeField = 43;
this.Container = container;
this.Name = "Default";
}
}


Public fields should be protected or private of course, but let think about this later.
You can ask “why we create public A Container field in AUnit”? We create field public string Name{get;private set;} (actually property but nevermind). And also we would like to be able to change value of this field for example method [Class AUnit] public bool Rename(string newName)();. The main idea of this method is changing Name field only that case if no one element in array (public List Units; ) has the same name like newName. But to achieve this, Rename method has to have access to all names that is currently used. And that is why we need Container field.

Code of extended version AUnit

class AUnit
{
public int SomeField;
public A Container;
public string Name { get; private set; }
public AUnit(A container)
{
this.SomeField = 43;
this.Container = container;
this.Name = "Default";
}
public bool Rename(String newName)
{
Boolean res = true;
foreach (AUnit unt in this.Container.Units)
{
if (unt.Name == newName)
{
res = false;
break;
}
}
if (res) this.Name = String.Copy(newName);
return res;
}
}


Ok. If you still read it let's continue. Now we need to create Class B and class BUnit which will be very similar like Class A and Class Aunit. And finally the main question of this puzzle is HOW WE CAN DO IT? Of course, I can CopyPaste and bit modify A and AUnit and create this code.

class B
{
public List<BUnit> Units; //Only Type Changing

public B()//ctor Name changing...
{
Units = new List<BUnit>();//Only Type Changing
}
public void Add()
{
this.Units.Add(new BUnit(this));//Only Type Changing
}
}
class BUnit
{
public int SomeField;
public B Container;//Only Type Changing
public string Name { get; private set; }
public A a; //NEW FIELD IS ADDED (just one)

public BUnit(B container) //Ctor Name and arguments type changing
{
this.SomeField = 43;
this.Container = container;
this.Name = "Default";

this.a=new A(); //New ROW (just one)
}
public bool Rename(String newName)
{
Boolean res = true;
foreach (BUnit unt in this.Container.Units) //Only Type Changing
{
if (unt.Name == newName)
{
res = false;
break;
}
}
if (res) this.Name = String.Copy(newName);
return res;
}
}


And I can to use this classes this way.

static void Main(string[] args)
{
B b = new B();
b.Add();
b.Units[0].a.Add();
b.Units[0].a.Units[0].SomeField += 100;
bool res= b.Units[0].a.Units[0].Rename("1");
res = b.Units[0].a.Units[0].Rename("1");

Console.ReadKey();
}


This construction is can be used to create “non-homogeneous trees”.

Help, I need somebody help, just no anybody…. [The Beatles]

I created B and BUnit using CopyPaste.
But how it can be done using “macro-definitions” or “Generic”, inherit or anything else in elegant style? (C# language)
I think that there is no reason to describe all my unsuccessful attempts and subquestions. Already topic is too long. : )

Thanks a lot if you still read it and understand what I would like to ask.

Answer

You need to implement a base type, lets call it UnitBase, with all common functionality. I'd structure your code the following way:

  1. Create an interface for your container, this way you can change implementation to more performant solutions without modifying the elements you will be adding to the container.

    public interface IContainer
    {
        Q Add<Q>() where Q : UnitBase, new();
        IEnumerable<UnitBase> Units { get; }
    }
    
  2. Following the idea stated in 1, why not make the search logic belong to the container? It makes much more sense, as it will mostly depend on how the container is implemented:

    public interface IContainer
    {
        Q Add<Q>() where Q : UnitBase, new();
        IEnumerable<UnitBase> Units { get; }
        bool Contains(string name);
    }
    

    A specific implementation of IContainer could be the following:

    public class Container : IContainer
    {
        public Container()
        {
            list = new List<UnitBase>();
        }
    
        private List<UnitBase> list;
    
        public Q Add<Q>() where Q: UnitBase, new()
        {
            var newItem = Activator.CreateInstance<Q>();
            newItem.SetContainer(this);
            list.Add(newItem);
            return newItem;
        }
    
        public IEnumerable<UnitBase> Units => list.Select(i => i);
        public bool Contains(string name) =>
            Units.Any(unit => unit.Name == name);
    }
    
  3. Create a base class for your AUnit and BUnit types condensing all common functionality:

    public abstract class UnitBase
    {
        protected UnitBase()
        {
        }
    
        public IContainer Container { get; private set; }
        public int SomeField;
        public string Name { get; private set; }
        public void SetContainer(IContainer container)
        {
            Container = container;
        }
    
        public bool Rename(String newName)
        {
             if (Container.Contains(newName))
                 return false;
    
            this.Name = newName; //No need to use String.Copy
            return true;
        }
    }
    
  4. Implement your concrete types:

    public class BUnit : UnitBase
    {
        public int SpecificBProperty { get; private set; }
        public BUnit()
        {
        }
    }
    

Shortcomings of this approach? Well, the container must be of type <UnitBase>, I've removed the generic type because it really wasn't doing much in this particular case as it would be invariant in the generic type.

Also, keep in mind that nothing in the type system avoids the following:

myContainer.Add<BUnit>();
myContainer.Add<AUnit>();

If having two different types in the same container is not an option then this whole set up kind of crumbles down. This issue was present in the previous solution too so its not something new, I simply forgot to point it out.