Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
476 views
in Technique[技术] by (71.8m points)

c# - Dynamically call a method on a generic target

I have a generic interface ICommandHandler<> that will have a number of implementations each for processing a specific implementation of ICommand, e.g.:

public class CreateUserCommand : ICommand { ... }
public class CreateUserCommandHandler : ICommandHandler<CreateUserCommand> { ... }

When I'm given an ICommand object I'm trying to dispatch it dynamically to the correct ICommandHandler. At the moment I've used a pretty straightforward reflection approach with an Invoke in my dispatcher class:

public void Dispatch<T>(T command) where T : ICommand
{
    Type commandType = command.GetType();
    Type handlerType = typeof(ICommandHandler<>).MakeGenericType(commandType);
    object handler = IoC.Get(handlerType);
    MethodInfo method = handlerType.GetMethod("Handle");

    method.Invoke(handler, new object[] { command });
}

There are 2 problems with this approach. Firstly it uses slow reflection. Secondly if the method throws any kind of exception then it'll be wrapped in a TargetInvocationException and I'll lose the stack trace if I re-throw it.

I worked out a way to make the call by creating a delegate and using DynamicInvoke but this doesn't solve the problem with exceptions (and I'm not sure DynamicInvoke is really any better than Invoke):

public void Dispatch<T>(T command) where T : ICommand
{
    Type commandType = command.GetType();
    Type handlerType = typeof(ICommandHandler<>).MakeGenericType(commandType);
    object handler = IoC.Get(handlerType);
    MethodInfo method = handlerType.GetMethod("Handle");

    Type actionType = typeof(Action<>).MakeGenericType(commandType);
    Delegate action = Delegate.CreateDelegate(actionType, handler, method);
    action.DynamicInvoke(command);
}

My question is, is there a better way to achieve what I'm trying to do? Preferably I could make a strongly-typed call instead of getting an object and looking up the MethodInfo. I assume that's not possible though because the type isn't know at compile time.

If that's not possible then an efficient solution that would throw the exception more 'natively' would be the next best option.

Edit: Updated code samples to clarify that I'm using IoC (Ninject) to create the ICommandHandler at runtime, not Activator.CreateInstance() as I first put. Included an example of how this would be used as requested:

var command = new CreateUserCommand() { Name = "Adam Rodger" };
var dispatcher = new CommandDispatcher();
dispatcher.Dispatch(command);
// this would send the message to CreateUserCommandHandler.Handle(command) 
// dynamically and any exceptions would come back 'natively'

Edit 2: As suggested below, I can't cast the result of IoC.Get(handlerType) to ICommandHandler<T> because I get an InvalidCastException at runtime. This is because at runtime T is actually ICommand, I assume because the command classes are arriving over WCF and somehow manage to lose their strong typing. The code that calls the dispatcher looks something like:

[ServiceContract]
public class CommandService
{
    [OperationContract]
    public void Execute(ICommand command) // no type information
    {
        var dispatcher = new CommandDispatcher(); // injected by IoC in real version
        dispatcher.Dispatch(command);
    }
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Most DI containers (including Ninject) allow you to do something like this:

public void Dispatch<T>(T command) where T : ICommand
{
    ICommandHandler<T> handler = IoC.Get<ICommandHandler<T>>();
    handler.Handle(command);
}

If you don't know the type of command (in other words, if typeof(T) != command.GetType()), using double-dispatch is the easiest way:

class SomeCommand : ICommand
{
    // ...

    public void Dispatch(IoC ioc)
    {
        var handler = ioc.Get<IHandle<SomeCommand>>();
        handler.Handle(this);
    }
}

but you could go with reflection if you find adding this code to all Commands distasteful.

Edit Here is a reflection-based version. You can (and should) cache the compiled delegate.

interface ICommand { }
interface IHandle<TCommand> where TCommand : ICommand
{
    void Handle(TCommand command);
}

class CreateUserCommand : ICommand { }
class CreateUserHandler : IHandle<CreateUserCommand>
{
    public void Handle(CreateUserCommand command)
    {
        Console.Write("hello");
    }
}

[TestMethod]
public void build_expression()
{
    object command = new CreateUserCommand();
    object handler = new CreateUserHandler();

    Action<object, object> dispatcher = BuildDispatcher(command.GetType());
    dispatcher(handler, command);
}

private static Action<object, object> BuildDispatcher(Type commandType)
{
    var handlerType = typeof(IHandle<>).MakeGenericType(commandType);
    var handleMethod = handlerType.GetMethod("Handle");

    var param1 = Expression.Parameter(typeof(object));
    var param2 = Expression.Parameter(typeof(object));

    var handler = Expression.ConvertChecked(param1, handlerType);
    var command = Expression.ConvertChecked(param2, commandType);
    var call = Expression.Call(handler, handleMethod, command);

    var lambda = Expression.Lambda<Action<object, object>>(call, param1, param2);
    return lambda.Compile();
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...