Search code examples
c#inheritancetypescasting

List<Parent> holding Child element without losing properties C#


I have these classes

class Start
{
    public List<Base> list { get; set; }
    public Start() 
    {
        list = new List<Base>();
    }
}

public abstract class Base
{
    public int a { get; set; }
}

class B : Base
{
    public int b;
    public B(int a, int b) { this.a = a; this.b = b; }
}

class C : Base
{
    public int c;
    public C(int a, int c) { this.a = a; this.c = c; }
}

I want list property of class Start to hold instances of class B or instances of class C (not both together, but it may hold the same type of any of B or C)
If possible, I don't want to use Generics
In C#, This is possible:

List<Object> lst = new List<Object>();
lst.Add(1);
list.Add("Text");
Console.WriteLine("{0} {1}", lst[0], lst[1]);

I don't understand why I can't make a similar behavior here:

Start s = new Start();
B b = new B(1, 2);
s.list.Add(b);
Console.WriteLine(s.list[0].a); //works
Console.WriteLine(s.list[0].b); //doesn't work

Solution

  • The difference between the two snippets is that in the first one you are not accessing any type-specific information (fields/properties/methods), i.e. something like the following will not compile too:

    List<Object> lst = new List<Object>();
    lst.Add(1);
    list.Add("Text");
    // will not compile despite string having Length property:
    Console.WriteLine("{0} {1}", lst[0], lst[1].Length); 
    

    a is common property declared in Base class, so it is available for every child of Base, if you want to access child specific properties you need to type test/cast :

    Start s = new Start();
    B b = new B(1, 2);
    s.list.Add(b);
    Console.WriteLine(s.list[0].a); //works
    if(s.list[0] is B b)
    {
        Console.WriteLine(b.b);
    }
    

    or make Start generic:

    class Start<T> where T: Base
    {
        public List<T> list { get; set; }
        public Start() 
        {
            list = new List<T>();
        }
    }
    
    var s = new Start<B>();
    s.list.Add(new B(1, 2));
    Console.WriteLine(s.list[0].b);
    

    P.S.

    Note that overriding ToString in Base, B and A will make Console.WriteLine("{0}", s.list[0]); "work":

    class B : Base
    {
        // ...
        public override string ToString() => return $"B(A: {a} B: {b})";
    }
    
    class C : Base
    {
        // ...
        public override string ToString() => return $"C(A: {a} B: {c})";
    }
    
    Start s = new Start();
    B b = new B(1, 2);
    s.list.Add(b);
    s.list.Add(new C(4, 2));
    Console.WriteLine("{0} {1}", s.list[0], s.list[1]); // prints "B(A: 1 B: 2) C(A: 4 B: 2)"
    

    So possibly you can introduce some method in Base which will allow you to use List<Base> (hard to tell without knowing actual use case).