xareth
xareth

Reputation: 112

Using child type in generic function from a List<Parent> object

My problem is similar to Using Child methods from a List<Parent> objects except rather than call a method I want to use the child type in a generic method. For example:

class Parent { }
class Child1 : Parent { }
class Child2 : Parent { }
// could be other child classes, unknown to the Process class

class Process
{
    public Process()
    {
        List<Parent> L = new List<Parent>();
        Child1 C1 = new Child1();
        Child2 C2 = new Child2();
        L.Add(C1);
        L.Add(C2);

        foreach(Parent P in L)
        {
            DoSomething(P);
        }
    }

    public void DoSomething<TypeP>(TypeP P)
    {
        // P is 'Child1', 'Child2' or potentially something else
        // TypeP is 'Parent'

        //TypeP = P.GetType(); //doesnt work
        //DoSomethingElse<P.GetType()>(); //doesnt work
        DoSomethingElse<TypeP>();
    }

    public void DoSomethingElse<T>()
    {
        // T is 'Parent'
        T R = Activator.CreateInstance<T>(); // want R to be 'Child1' or 'Child2'
    }
}

I'd like DoSomethingElse to be called with the type of C1 or C2 rather than the the 'Parent' type. I've been round a few times but cant find a way to get this to work. I feel like it should be trivial given P in DoSomething is the correct type.

EDIT: The reason DoSomethingElse is generic is because there could be any number of child classes derived from the parent. I used Child1 and Child2 as an example, but in reality there could be quite a few more and so a switch statement in DoSomethingElse isn't feasible because the number of derived classes is unknown.

Upvotes: 2

Views: 2796

Answers (4)

Fabjan
Fabjan

Reputation: 13676

There are couple of things wrong with the code above. Generics only work for types known at compile time. We upcast all our values to base class here:

// L is a List<Parent>
L.Add(C1);
L.Add(C2);

So if we use generics in DoSomething<TypeP> compiler will always use Parent for TypeP. We'd need to work with type known only on run time, our options are limited. Something that is called DLR is used for dealing with run-time values. The type that DLR works with is called dynamic.

So let's change the foreach iteration variable to dynamic:

foreach (dynamic P in L)
{
    DoSomething(P);
}

Now we can use generics with constraint that TChild should be derived from Parent:

public void DoSomething<TChild>(TChild P) where TChild : Parent
{    
    ...
    DoSomethingElse(P);
}

We cannot use this method signature as it will require us to specify the type on method invocation:

public void DoSomethingElse<TChild>()

To let compiler infer type we can pass instance of TChild that compiler can use to infer type.:

public void DoSomethingElse<TChild>(TChild P) where TChild : Parent
{
    // TChild is Child now
    var R = Activator.CreateInstance(typeof(TChild)); // want R to be 'Child1' or 'Child2'
}

Here is DotNetFiddle example

Upvotes: 3

Yury Schkatula
Yury Schkatula

Reputation: 5369

There are two options here:

  1. Replace DoSomethingElse with a virtual method at Parent class, so any child has to overwrite the behavior you need. Then it's usual thing to just call that overridden method via parent-typed instance (Liskov substitution principle). Preferred way, IMHO.

    public void DoSomething<TypeP>(TypeP P)
    {
        P.DoSomethingElse(); // this is virtual method to be overwritten by children types
    }
    
  2. "Generics specialization" approach (that seems a bit hacky for me as C# naturally lacks of some C++ features, but... you may find it suitable) https://stackoverflow.com/a/3337928/1964969

Upvotes: 1

Chris
Chris

Reputation: 27619

In your specific case you want to call Activator.CreatInstance<T>() but there is an alternative. There is a method [Activator.CreatInstance(Type type)][1].

You can use this by changing your dosomething method slightly

public void DoSomethingElse(Parent p)
{
    var R = Activator.CreateInstance(p.GetType());
}

Of course without knowing what you then are doing with it I don't know if this necessarily helps. You may still need some kind of switch statement on the type of R to then make use of this new object. If however you can from this point on just treat R as being of type Parent then this should help you out.

Upvotes: 0

Oscar
Oscar

Reputation: 13970

You could check for type using is keyword:

public void DoSomething<TypeP>(TypeP P)
        {
            if (P is Child1)
            {

            }
            else if(P is Child2)
            {

            }
        }

Upvotes: 0

Related Questions