【问题标题】:Generic message handlers通用消息处理程序
【发布时间】:2015-02-17 15:10:55
【问题描述】:

我想创建一个通用机制来处理 C# 中的消息。我在我的小应用程序中需要这个,所以我不想使用完整的消息总线。我的要求很简单:

  • 我希望有几个消息类,即Message1Message2。他们可以从一个基类继承,这不是问题,但如果他们不这样做,我不在乎。目前他们确实继承自 Message
  • 能够为每个消息类获取处理程序。即,如果我发送Message1,那么应该实例化Message1Handler 类。处理程序必须实现IMessageHandler<T>,其中T 是消息类。 IMessageHandler 定义如下:

    interface IMessageHandler<T>
    {
        void Execute(T message);
    }
    

我写了一个简单的“解析器”类:

public static class HandlerRegistry
{
    private static readonly Dictionary<string, Type> _handlers = new Dictionary<string, Type>(); 


    public static void Register<T, T2>() where T2: IMessageHandler<T>
    {
        _handlers.Add(typeof(T).FullName, typeof(T2));

    }

    public static IMessageHandler<T> Resolve<T>(T parameters)
    {
        var type = _handlers[parameters.GetType().FullName];
        return (IMessageHandler<T>) Activator.CreateInstance(type);
    }
}

在这个实现中,一切都很好,但有一部分 - 转换为 IMessageHandler。当我尝试将它与消息集合一起使用时,会发生这种情况:编译器在编译时不知道集合中将包含哪些实际消息 - 它只是假设它们都是 Message 的子类,所以它试图将IMessageHandler&lt;ConcreteMessage&gt; 转换为IMessageHandler&lt;Message&gt;,显然我遇到了无效转换的异常。在这种情况下,逆变可能会有所帮助,但我无法将参数声明为 out,因为我在 Execute 方法参数中有消息。

有人知道这个问题的优雅解决方案吗?我知道我可以让它“更多运行时间”——而不是使用泛型只是声明 void Execute(Message m) 并且在每个处理程序中,都从尝试转换为我期望的类型开始,但是正如有人在某处所说的那样-您编写的每一个转换都破坏了使用类型系统的全部意义。

【问题讨论】:

  • 相关(只需阅读现在提到“命令”的“消息”):cuttingedge.it/blogs/steven/pivot/entry.php?id=91
  • 这是相关的,但据我所知,他从未真正触及我的问题。他总是对从 IoC 注入的 ICommandHandler 接口进行操作。
  • 这里不需要泛型......它没有添加任何东西......
  • 我不会这样写,但它是一个很好的参考...stackoverflow.com/questions/1477471/…
  • 顺便问一下,这是每个消息类型的单个处理程序,还是多个处理程序?

标签: c# generics


【解决方案1】:

选项 1

如果您不关心使用反射。您可以将Execute 方法添加到您的HandlerRegistry,而不是将处理程序返回给调用者:

public static void Execute<T>(T parameters)
{
    var type = _handlers[parameters.GetType().FullName];
    var handler = Activator.CreateInstance(type);
    type.GetMethod("Execute", new[] { parameters.GetType() })
        .Invoke(handler, new object[] { parameters });
}

选项 2

如果您不关心一个消息处理程序只能订阅一条消息。我们可以利用 C# 的Explicit Interface Implementation 特性:

// NOTE: This interface is not generic
public interface IMessageHandler
{
    void Execute(object message);
}

public abstract class MessageHandler<T> : IMessageHandler
{
    public abstract void Execute(T message);

    // NOTE: Here we explicitly implement the IMessageHandler
    void IMessageHandler.Execute(object message)
    {
        Execute((T)message);
    }
}

现在您的解析方法可以更改为:

public static IMessageHandler Resolve<T>(T parameters)
{
    var type = _handlers[parameters.GetType().FullName];
    return (IMessageHandler)Activator.CreateInstance(type);
}

顺便说一句,我个人更喜欢传入 Type 而不是消息实例。

然后让你的处理程序继承通用抽象MessageHandler&lt;T&gt;,而不是实现IMessageHandler

public class HandlerA : MessageHandler<MessageA>
{
    public override void Execute(MessageA message)
    {
        Console.WriteLine("Message A");
    }
}

public class HandlerB : MessageHandler<MessageB>
{
    public override void Execute(MessageB message)
    {
        Console.WriteLine("Message B");
    }
}

【讨论】:

  • 第一种方式是最合理的(在方法内部执行handler),但是这里也不需要泛型。
  • 我也更喜欢第一种方法。大多数时候反思并没有那么糟糕:)
【解决方案2】:

消息路由器怎么样:

 class Tester
    {
        public void Go()
        {
            var a = new MessageA();
            var b = new MessageB();
            var c = new MessageC();

            var router = new MessageRouter();
            router.RegisterHandler(new HandlerA());
            router.RegisterHandler(new HandlerB());

            router.Route(a);
            router.Route(b);
            router.Route(c);
        }
    }

    class MessageRouter
    {
       Dictionary<Type, dynamic> m_handlers = new Dictionary<Type,dynamic>();

        public void RegisterHandler<T>(IMessageHandler<T> handler)
        {
            m_handlers.Add(typeof(T), handler);
        }

        public void Route(dynamic message)
        {
            var messageType = message.GetType();
            if (m_handlers.ContainsKey(messageType))
            {
                m_handlers[messageType].Handle(message);
            }
            else
            {
                foreach (var pair in m_handlers)
                {
                    if(pair.Key.IsAssignableFrom(messageType))
                    {
                        pair.Value.Handle(message);
                    }
                }
            }
        }

    }

    class MessageA
    {
        public virtual string A { get { return "A"; } }
    }
    class MessageB
    {
        public  string B { get { return "B"; } }
    }

    class MessageC :MessageA
    {
        public  override string A {  get { return "C"; } }
    }
    interface IMessageHandler<T>
    {
        void Handle(T message);
    }
    class HandlerA : IMessageHandler<MessageA>
    {

        public void Handle(MessageA message)
        {
            Console.WriteLine(message.A);
        }
    }
    class HandlerB : IMessageHandler<MessageB>
    {

        public void Handle(MessageB message)
        {
            Console.WriteLine(message.B);
        }
    }

【讨论】:

  • 这是一个不错的解决方案。您在这里没有假装任何东西,只需使用动态调用来完成这项工作。我也喜欢它不需要反思的事实。我不喜欢的是注册过程相当复杂,不可扩展。
【解决方案3】:

采取稍微不同的方法怎么样:与其注册处理程序的类型,为什么不注册将处理消息的实际处理程序实例?这为您在处理程序的实例化方面提供了更大的灵活性,并消除了任何类型的歧义。

我们的想法是能够做到这一点:

// have several handler classes
class FooMessageHandler : IMessageHandler<Foo>
{  }

class BarMessageHandler : IMessageHandler<Bar>
{  }

// have them instantiated - allows you to pass much more context
// than Activator.CreateInstance is able to do    
var fooMessageHandler = new FooMessageHandler(various params);
var barMessageHandler = new BarMessageHandler(various params);

// register actual instances    
HandlerRegistry.Register<Foo>(fooMessageHandler);
HandlerRegistry.Register<Bar>(barMessageHandler);

// handler registry will simply dispatch the message to
// one of the handlers        
HandlerRegistry.Dispatch(someFooMessage);

不仅如此,该方法还允许您为每种消息类型注册多个处理程序:

// these will all get called when a Foo message is received    
HandlerRegistry.Register<Foo>(fooMessageHandler);
HandlerRegistry.Register<Foo>(someOtherFooHandler);
HandlerRegistry.Register<Foo>(yetAnotherFooHandler);

【讨论】:

    【解决方案4】:

    如果您从通用抽象 MessageBase 继承所有消息,而不是使消息处理程序接口 IMessageHandler&lt;T&gt; 通用,而是对 Execute 方法本身施加约束,该怎么办?

    void Execute&lt;T&gt;(T message) where T : MessageBase

    通过这种方式,您可以获得所需的功能,并且您的消息处理程序解析器 HandlerRegistry 只需要稍作调整。只需将返回类型和约束从 IMessageHandler&lt;T&gt; 更改为 IMessageHandler

    以下对MessageBaseIMessageHandlerHandlerRegistry稍作修改。

    (相关的dotnetfiddle在这里https://dotnetfiddle.net/e6M1UA

    // Message
    public abstract class MessageBase
    {
        public virtual void Action() // ...for examples sake
        {
            Console.WriteLine(GetType().Name);
        }
    }
    
    // Message handler
    public interface IMessageHandler
    {
        void Execute<T>(T message) where T : MessageBase;
    }
    
    // Resolver
    public static class HandlerRegistry
    {
        private static readonly Dictionary<string, Type> Handlers = 
            new Dictionary<string, Type>();
    
        public static void Register<T, T2>() where T2 : IMessageHandler
        {
            Handlers.Add(typeof(T).FullName, typeof(T2));
        }
    
        public static IMessageHandler Resolve<T>(T parameters)
        {
            var type = Handlers[parameters.GetType().FullName];
            return (IMessageHandler)Activator.CreateInstance(type);
        }
    }
    

    现在,如果您使用例如以下实现

    public class Message1 : MessageBase
    {}
    
    public class Message2 : MessageBase
    {
        public override void Action()
        {
            Console.Write(@"Overriding ");
            base.Action();
        }
    }
    
    public class Message1Handler : IMessageHandler
    {
        public void Execute<T>(T message) where T : MessageBase
        {
            Console.Write(@"MessageHandler1 > ");
            message.Action();
        }
    }
    
    public class Message2Handler : IMessageHandler
    {
        public void Execute<T>(T message) where T : MessageBase
        {
            Console.Write(@"MessageHandler2 > ");
            message.Action();
            Console.WriteLine(@"...and then some");
        }
    }
    

    有了这段代码

    HandlerRegistry.Register<Message1, Message1Handler>();
    HandlerRegistry.Register<Message2, Message2Handler>();
    
    var messages = new List<MessageBase>()
        {
            new Message1(),
            new Message2()
        };
    
    foreach (var message in messages)
    {
        var handler = HandlerRegistry.Resolve(message);
        handler.Execute(message);               
    }
    

    你会得到控制台日志

    MessageHandler1 > 消息1
    MessageHandler2 > 覆盖 Message2
    ...然后是一些

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-03-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-12-10
      • 1970-01-01
      相关资源
      最近更新 更多