Search code examples
c#oopsubclassinstantiationbase-class

How to create a new object instance of class being the same type of another instance by only knowing it as base class type?


How to dynamic down casting in C#?

I have these classes as follows:

public class Base
{
}

public class A : Base
{
    public string name;
}

public class B : Base
{
    public string name;
}

public class C : Base
{
    public string name;
}

I also have a Go method.

I want to make it dynamic child class type(A, B, C), not "Base" type.

So that any child class can work.

How do I make testA have a datatype dynamically?

public class Main
{
    private void Go()
    {
        Test(new A{ name = "My Name is A"});
    }

    private void Test(Base base)
    {
        var baseType = base.GetType();            // Class A (or Class B, Class C)
        var baseTypeName = base.GetType().Name;   // "A" (or "B", "C")

        // I want to make it dynamic child class type(A, B, C), not "Base" type.
        // So that any child class can work
        // How?

        // This code is fixed for A.
        var testA = new A();
        testA.name = "Sucess";

        // I want testA dynamic Data Type.
        var testA = ?;
        testA.name = "Sucess";
    }
}

Solution

  • Downcasting is not possible in C#.

    A base object is less than a child object, so you cannot create a base object and cast it to a child.

    A base class is missing from the properties and methods added in childs, so you cannot create a base object and convert it to be a child object.

    A sofa can be a chair to sit on it, but a chair can not be a sofa to lie down.

    But upcasting is OOP fundamental: we can say that child is type of base.

    Here you don't want to up or down cast, but create a new instance as formulated in the suggested new title for the question.

    So to create another instance of the real base object type and not a base you can use reflexion and Activator class.

    Example to improve in first intention the code provided

    public class Base
    {
      public string Name;
    }
    
    public class Child : Base
    {
    }
    
    void Go()
    {
      var a = new Child { Name = "My Name is A" }; // Legal upcast: Child is type of Base
      var b = (Child)Test(a);
      b.Name = "Sucess";
      Console.WriteLine(a.Name);
      Console.WriteLine(b.Name);
    }
    
    Base Test(Base instance)
    {
      return (Base)Activator.CreateInstance(instance.GetType());
    }
    

    But we can't write:

    Base c = new Base();
    Child d = (Child)c;  // Illegal downcast: Base is not type of Child
    

    Output

    My Name is A
    Sucess
    

    The use of a generic will be strongly typed and adapted to solve the problem as exposed

    void Go()
    {
      var a = new Child { Name = "My Name is A" };
      var b = Test(a);
      Console.WriteLine(a.Name);
      Console.WriteLine(b.Name);
    }
    
    T Test<T>(T instance) where T : Base
    {
      var instanceNew = Activator.CreateInstance<T>();
      instanceNew.Name = "Sucess";
      return instanceNew;
    }
    

    Because using classes, it can be simplified:

    T Test<T>(T instance) where T : Base, new()
    {
      var instanceNew = new T();
      instanceNew.Name = "Sucess";
      return instanceNew;
    }
    

    But be carefull that this generic solution does not work when passing an object as an ancestor class:

    var b = Test((Base)a);
    

    Will be processed as Base and not Child.

    So we can mix non-generic solution with generic behavior to write:

    T Test<T>(T instance) where T : Base, new()
    {
      var instanceNew = (T)Activator.CreateInstance(instance.GetType());
      instanceNew.Name = "Sucess";
      return instanceNew;
    }
    

    Thus that works better now and it is more consistent:

    Base a = new Child { Name = "My Name is A" };
    Child b = (Child)Test(a);
    Console.WriteLine(a.Name);
    Console.WriteLine(b.Name);
    

    Output

    My Name is A
    Sucess
    

    Possible simplification

    Unless you want to do a particular processing in the Test method, on the real type of the parameter, for a new instance by only knowing this parameter as a type of Base, to be able to pass any child, and then return it, to refactor some things for example, you can direclty write:

    var a = new Child { Name = "My Name is A" };
    
    var b = (Child)Activator.CreateInstance(a.GetType());
    
    b.Name = "Success";
    

    Note: please, don't use base as a variable name because it is a reserved language keyword, so it does not compile - also, it is an opinion, avoid the use of @base because it is not clean, and in fact it can be source of problems and confusions.