Dynamic generic interface cast

2019-06-01 21:09发布

问题:

I want to dynamically discover and register interface implementations. For the sake of argument I have two methods similar to these:

public void Register<TEvent>(IHandler<TEvent> handler) where TEvent : IEvent

public void Register<TEvent>(Action<TEvent> action) where TEvent : IEvent
{
    Register<TEvent>(handler.Handle);
}

And the interfaces are like the following:

public interface IHandler<T> where T : IEvent
{
    void Handle(T args);
}

public interface IEvent
{
}

Then I have the concrete implementations like:

public class ChangedEvent : IEvent
{...}

public class ChangedHandler : IHandler<ChangedEvent>
{
    public void Handle(ChangedEvent args)
    {

    }
}

I can then discover all concrete implementations of IHandler<> in my assemblies, and I wanted to do something like this:

IList<Type> types = TypeFinder.GetImplementors(typeof(IHandler<>));
foreach (Type type in types)
{
    object instance = Activator.CreateInstance(type);
    Listeners.Register((IHandler<IEvent>)instance);
}

The code will compile, it's not invalid, but in run time the cast fails because it's invalid. However, if I cast to a concrete IEvent like:

IList<Type> types = TypeFinder.GetImplementors(typeof(IHandler<>));
foreach (Type type in types)
{
    object instance = Activator.CreateInstance(type);
    Listeners.Register((IHandler<ChangedEvent>)instance);
}

This cast is valid, and it will run. The problem is the dynamic of the scenario, I want to be able to discover the types and register them. I didn't want to create a non-generic interface for the handler, but I believe this is an impossible scenario because the framework doesn't have enough information to infer the required types. Is there any way to achieve this, or do you have any sugestions to achieve the desired result?

Many thanks.

回答1:

This doesn't work because of Covariance and contravariance, imagine this worked and you did the following.

public class AnotherTypeOfEvent : IEvent      {...} 

public void Register<TEvent>(IHandler<TEvent> handler) where TEvent : IEvent 
{
    //Really our runtime type expects ChangedEvent, but our constraint is
    //only for IEvent so you could do this - oh dear..
    handler.Handle(new AnotherTypeOfEvent());     
}

Listeners.Register((IHandler<IEvent>)new ChangedHandler());   

You would be passing AnotherTypeOfEvent into you ChangedHandler.Handle method, which clearly expects ChangedEvent, this would cause all sorts of problems.



回答2:

Why don't you specify in typefinder the type that you want to get, as you will cast it after ?

IList<Type> types = TypeFinder.GetImplementors(typeof(IHandler<IEvent>));
foreach (Type type in types)
{
    object instance = Activator.CreateInstance(type);
    Listeners.Register((IHandler<IEvent>)instance);
}


回答3:

I am not sure what error you are getting. This compiles fine for me:

public interface IEvent
{

}

public interface IHandler<T>
{

}

public class Test //: ITest
{
    public void Register<TEvent>(IHandler<TEvent> handler) where TEvent : IEvent
    {

    }
}

public class ChangedEvent : IEvent
{


}
public class Example
{
    public static void Main()
    {
        Test t = new Test();
        Type[] types = new Type[10];
        foreach (Type type in types)
        {
            object instance = Activator.CreateInstance(type);
            t.Register((IHandler<IEvent>)instance);
        }
    }
}