Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET generics: how to resolve type T in run-time?

Let me explain you in the following example what problem I'm solving:

class Animal {}
class Cat: Animal {}
class Dog : Animal { }

interface IAnimalHandler<in T> where T: Animal
{
    void Handle(T animal);
}

class AnimalHandler : 
    IAnimalHandler<Cat>,
    IAnimalHandler<Dog>
{
    public void Handle(Cat animal)
    {
        Console.Write("it's a cat !");
    }

    public void Handle(Dog animal)
    {
        Console.Write("it's a dog !");
    }
}

So now I want go through all animals and run appropriate handler like this:

  var ah = new AnimalHandler();
  var animals = new List<Animal> { new Cat(), new Dog() };
  animals.ForEach(a => ah.Handle(a));

However this code would not work (Can not resolve method Hanler<>...) just because .NET compiler needs to know before compilation which type is used here, so what might be the best solution for this issue? In other words, I need to ask .NET compiler to take appropriate handler of type T for every instance of type T in run-time. I do not want to use multiple if statements checking instance type.

UPDATE: Sorry for missing it, it seemed obvious to me, but now I understand it's not so obvious: AnimalHandler class contains logic not supposed to be part of domain objects Cat and Dog. Think about them as pure plain domain objects, I do not want them to know about any sort of handlers

like image 790
YMC Avatar asked Jul 17 '12 17:07

YMC


People also ask

How do you determine what type a generic parameter T is?

You can get the Type that represents T , and use the IsInterface property: Type type = typeof(T); if (type. IsInterface) { ... } If you want to know which interface is passed, just use == to compare the Type objects, e.g.

Is Genericity resolved at compile time?

However, according to this answer, in C#, the generic type is resolved at compile time.

Are generics runtime or compile time?

Due to type erasure, there are no generics at runtime. It is all compile-time type checking.

What does the T mean in C#?

T is called type parameter, which can be used as a type of fields, properties, method parameters, return types, and delegates in the DataStore class. For example, Data is generic property because we have used a type parameter T as its type instead of the specific data type. Note.


3 Answers

You can use C# 4 dynamic to move the overload resolution step from compile-time to runtime:

var ah = new AnimalHandler();
var animals = new List<Animal> { new Cat(), new Dog() };
animals.ForEach(a => ah.Handle((dynamic)a));
like image 147
Daniel Avatar answered Sep 25 '22 16:09

Daniel


To me it sounds like you could benefit from this pattern (implemented using StructureMap). Going from your original statement, "I need to ask .NET compiler to take appropriate handler of type T for every instance of type T in run-time" it might look something like this:

class Dog : Animal { }
class Cat : Animal { }

interface IHandler<T>
{
    void Handle(T eval);
}

class DogHandler : IHandler<Dog>
{
    public void Handle(Dog eval)
    {
        // do whatever
    }
}

class CatHandler : IHandler<Cat>
{
    public void Handle(Cat eval)
    {
        // do whatever
    }
}    

You could then configure StructureMap as per the linked article, and get the appropriate handler using:

var dogHandler = _container.GetInstance<IHandler<Dog>>(); // instance of DogHandler
var catHandler = _container.GetInstance<IHandler<Cat>>(); // instance of CatHandler

UPDATE: To resolve these in a loop you could do something like this:

foreach (var animal in animals)
{
    var concreteHandlerType = typeof(IHandler<>).MakeGenericType(animal.GetType());
    var handler = _container.GetInstance(concreteHandlerType);
    handler.Handle(animal);
}

I use this pattern in a fairly large system to accomplish the same goals (pure domain objects, handlers for logic that should not be inside those domain objects, simplified maintenance). It works well in a system where you want to have a separate handler class for each object.

like image 26
Shane Fulmer Avatar answered Sep 25 '22 16:09

Shane Fulmer


Exactly your code, but using reflection:

var ah = new AnimalHandler();
var animals = new List<Animal> { new Cat(), new Dog() };
animals.ForEach(a => {
  var method = ah.GetType().GetMethod("Handle", new Type[] {a.GetType()});
  method.Invoke(ah,new object[] { a });
});
like image 24
sblom Avatar answered Sep 23 '22 16:09

sblom