Reputation: 112
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
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
Reputation: 5369
There are two options here:
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
}
"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
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
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