2013-04-10 6 views
4

У меня есть универсальный интерфейс ICommandHandler<>, который будет иметь несколько реализаций каждого для обработки конкретной реализации ICommand, например:Динамически вызова метода общей цели

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

Когда я Дано ICommand объект Я пытаюсь отправить его динамически на правильный ICommandHandler. На данный момент я использовал довольно простой подход отражения с 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"); 

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

Есть 2 проблемы с этим подходом. Во-первых, он использует медленное отражение. Во-вторых, если метод выбрасывает какое-либо исключение, он будет завернут в TargetInvocationException, и я потеряю трассировку стека, если я его повторно выброшу.

Я разработал способ, чтобы сделать вызов путем создания делегата и использование DynamicInvoke, но это не решает проблему с исключениями (и я не уверен, что DynamicInvoke действительно лучше, чем 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); 
} 

Мой вопрос в том, есть ли лучший способ достичь того, что я пытаюсь сделать? Предпочтительно я мог бы сделать строго типизированный вызов вместо получения object и поискать MethodInfo. Я предполагаю, что это невозможно, потому что тип не знает во время компиляции.

Если это невозможно, то лучшим вариантом будет эффективное решение, которое сделает исключение более «естественным».

Edit: обновленные образцы кода для уточнения того, что я использую IoC (Ninject) для создания ICommandHandler во время выполнения, а не Activator.CreateInstance() как я первый положил. Включенный пример того, как это будет использоваться в соответствии с просьбой:

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: Как предлагается ниже, я не могу бросить результат IoC.Get(handlerType) к ICommandHandler<T>, потому что я получаю InvalidCastException во время выполнения. Это связано с тем, что во время выполнения T на самом деле ICommand, я предполагаю, что классы команд прибывают через WCF и каким-то образом теряют свою сильную типизацию. Код, который вызывает диспетчеру выглядит что-то вроде:

[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); 
    } 
} 
+0

Я смущен тем, что это за цель. Можете ли вы добавить пример кода для использования этого шаблона? – Bobson

+0

Вы объявляете 'CreateUserCommandHandler', но я не вижу, как он создается. Кроме того, у вас есть 'Type handlerType = typeof (ICommandHandler <>). MakeGenericType (commandType);' но не будет ли это просто определять его как «ICommandHandler », с которым вы запускаете «Activator.CreateInstance»? Как создать экземпляр интерфейса? –

+0

Фактически, это код _actual_, который вы используете? Если 'T' - ваш тип' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '? –

ответ

6

Большинство DI контейнеры (включая Ninject) позволяют сделать что-то вроде этого:

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

Если вы не знаю тип команды (другими словами, если typeof(T) != command.GetType()), использование двойной отправки является самым простым способом:

class SomeCommand : ICommand 
{ 
    // ... 

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

но вы можете пойти с отражением, если найдете этот код ко всем командам, отвратительным.

Редактировать Реальная версия. Вы можете (и должны) кэшировать скомпилированный делегат.

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(); 
} 
+0

Я использовал отредактированную версию с построением выражения и отлично работает. Скорость кажется почти такой же, как правильный вызов метода (особенно при кэшировании результата), и все исключения выбрасываются должным образом. –

1

Попробуйте

dynamic handler=Activator.CreateInstance(handlerType); 
try 
    { 
     handler.Handle((dynamic)command); 
    } 

    catch 
    { 
    // do whatever you want 
    } 
+0

Я попытался использовать 'dynamic', но я только что получил исключение во время выполнения, сказав, что я поставил недопустимый аргумент' handler.Handle() '. Я предполагаю, что это происходит из-за того, что реализации выполняют «ICommand», тогда как диспетчер берет тип интерфейса. –

+0

Я редактировал мой anwser. Я на самом деле построил именно то, что вы пытаетесь сделать, и так я это сделал (конечно, строго выполнял обработчик команд) – MikeSW