通用消息处理程序

时间:2015-02-15 21:04:55

标签: c# generics

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

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

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

我写了一个简单的&#34; Resolver&#34;类:

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的{​​{1}},所以它试图将IMessageHandler<ConcreteMessage>投射到IMessageHandler<Message>,显然我收到了无效投射的异常。在这种情况下,可能的逆变会有所帮助,但我无法将参数声明为out,因为我在Execute方法参数中有消息。

有谁知道这个问题的优雅解决方案?我知道我可以做到#34;更多的运行时间&#34; - 而不是使用泛型只是声明 void Execute(Message m)并且在每个处理程序中都开始尝试强制转换为我期望的类型,但正如某人所说的那样 - 你写的每一个演员都破坏了使用类型系统的全部意义。

4 个答案:

答案 0 :(得分: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);
        }
    }

答案 1 :(得分:2)

选项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<T>,而不是实现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");
    }
}

答案 2 :(得分:1)

如何采用稍微不同的方法:为什么不注册将处理消息的实际处理程序实例,而不是注册处理程序的类型?这为处理程序的实例化提供了更大的灵活性,并消除了任何类型的歧义。

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

// 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);

答案 3 :(得分:0)

如果从公共抽象MessageBase继承所有消息而不是使消息处理程序接口IMessageHandler<T>通用,那么,如何对Execute方法本身设置约束,该怎么办?

void Execute<T>(T message) where T : MessageBase

通过这种方式,您可以获得所需的功能,而消息处理程序解析程序HandlerRegistry只需要进行一些小的调整。只需改变返回类型&amp;从IMessageHandler<T>IMessageHandler的约束。

以下略有修改MessageBaseIMessageHandlerHandlerRegistry

(相关dotnetfiddle here 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&gt; MESSAGE1
  MessageHandler2&gt;覆盖Message2
  ......然后是一些