代码之家  ›  专栏  ›  技术社区  ›  Matthew Goulart

如何将消息反序列化为强类型对象,然后动态调用该消息的运行时分配处理程序

  •  3
  • Matthew Goulart  · 技术社区  · 8 年前

    啊,多好的头衔…

    我在一个学习项目的服务器上工作。我花了很长时间想弄清楚如何恰当地提出这个问题。一开始,我甚至不知道我到底想实现什么。

    我使用的是一个包含n个组件(n>=0)的服务器。

    • 每个组件都是在运行时使用di添加的。
    • 每个组件都是一个“黑匣子”。当它收到一条消息时,它被认为是一个“工作单元”,并拥有从开始到结束所需的一切。
    • 每个组件负责提供 subscribing 到A message ,并提供 handler 为了那条信息。我计划使用 经办人 功能。

    “处理程序”的示例:

    [Handles(ExampleMessage)]
    private void handleExampleMessage(ExampleMessage message)
    {
        DoStuff();
    }
    

    这是我思考问题的最清晰的方式:

    我怎样才能达到 类型化的 “消息代理”系统,如asp.net mvc提供的 类型化的 模型到 controller action 从A 序列化的 输入。

    所以我想实现的是:

    Serialized message -gt; Strongly typed message -gt; message service -gt; call handler function with *strongly typed* message as an argument

    我想了几件事:

    我尝试的第一件事就是将消息反序列化为 dynamic ,但不进行非精简和编译时检查对于 动态 为了我。

    然后我尝试在运行时使用反射创建静态反序列化方法,并使用这些方法的返回值调用“处理程序”,但它得到了 太难看了 还有意大利面,我不得不放弃它(当然,如果有人有一个优雅的,注重表现的方式,我仍然可以选择)

    最后我试着用 Message 所有消息都继承自的类型,但当我尝试使用 Dictionary<Action<Message>, Message> 调用适当的处理程序

    1 回复  |  直到 8 年前
        1
  •  3
  •   Sam meh-uk    8 年前

    这是可能的,只是有点复杂。您要做的是在组件中搜索具有 Handles 属性,并通过反射调用它们。

    假设我们有以下接口:

    public interface IComponent
    {
    }
    
    public interface IMessage
    {
    };
    

    我们还要创建 手柄 属性,用于将方法标记为处理特定消息类型:

    [AttributeUsage(AttributeTargets.Method)]
    public class HandlesAttribute : Attribute
    {
        public Type MessageType { get; private set; }
    
        public HandlesAttribute(Type messageType)
        {
            MessageType = messageType;
        }
    };
    

    现在我们将创建一个消息代理,它将负责在给定的组件列表中查找所有消息处理方法。我们将使用反射来做这件事。首先我们会找到所有 手柄 属性,然后我们将检查它们是否具有所需的 IMessage 参数:

    public class MessageBroker
    {
        // Encapsulates a target object and a method to call on that object.
        // This is essentially our own version of a delegate that doesn't require
        // us to explicitly name the type of the arguments the method takes.
        private class Handler
        {
            public IComponent Component;
            public MethodInfo Method;
        };
    
        private Dictionary<Type, List<Handler>> m_messageHandlers = new Dictionary<Type, List<Handler>>();
    
        public MessageBroker(List<IComponent> components)
        {
            foreach (var component in components)
            {
                var componentType = component.GetType();
    
                // Get all private and public methods.
                var methods = componentType.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
                foreach (var method in methods)
                {
                    // If this method doesn't have the Handles attribute then ignore it.
                    var handlesAttributes = (HandlesAttribute[])method.GetCustomAttributes(typeof(HandlesAttribute), false);
                    if (handlesAttributes.Length != 1)
                        continue;
    
                    // The method must have only one argument.
                    var parameters = method.GetParameters();
                    if (parameters.Length != 1)
                    {
                        Console.WriteLine(string.Format("Method {0} has too many arguments", method.Name));
                        continue;
                    }
    
                    // That one argument must be derived from IMessage.
                    if (!typeof(IMessage).IsAssignableFrom(parameters[0].ParameterType))
                    {
                        Console.WriteLine(string.Format("Method {0} does not have an IMessage as an argument", method.Name));
                        continue;
                    }
    
                    // Success, so register!
                    RegisterHandler(handlesAttributes[0].MessageType, component, method);
                }
            }
        }
    
        // Register methodInfo on component as a handler for messageType messages.
        private void RegisterHandler(Type messageType, IComponent component, MethodInfo methodInfo)
        {
            List<Handler> handlers = null;
            if (!m_messageHandlers.TryGetValue(messageType, out handlers))
            {
                // If there are no handlers attached to this message type, create a new list.
                handlers = new List<Handler>();
                m_messageHandlers[messageType] = handlers;
            }
    
            var handler = new Handler() { Component = component, Method = methodInfo };
            handlers.Add(handler);
        }
    }
    

    上面的构造函数记录一条警告消息,并忽略与我们需要的签名不匹配的任何方法(即,一个从i message派生的参数)。

    现在,让我们添加一个处理消息的方法。这将使用 Invoke :

        // Passes the given message to all registered handlers that are capable of handling this message.
        public void HandleMessage(IMessage message)
        {
            List<Handler> handlers = null;
            var messageType = message.GetType();
            if (m_messageHandlers.TryGetValue(messageType, out handlers))
            {
                foreach (var handler in handlers)
                {
                    var target = handler.Component;
                    var methodInfo = handler.Method;
    
                    // Invoke the method directly and pass in the method object.
                    // Note that this assumes that the target method takes only one parameter of type IMessage.
                    methodInfo.Invoke(target, new object[] { message });
                }
            }
            else
            {
                Console.WriteLine(string.Format("No handler found for message of type {0}", messageType.FullName));
            }
        }
    };
    

    现在要测试它,我们将使用这些示例消息和组件。我还添加了一些错误配置的测试方法(即错误的参数):

    public class ExampleMessageA : IMessage
    {
    };
    
    public class ExampleMessageB : IMessage
    {
    };
    
    public class ExampleComponent : IComponent
    {
        [Handles(typeof(ExampleMessageA))]
        public void HandleMessageA(ExampleMessageA message)
        {
            Console.WriteLine(string.Format("Handling message of type ExampleMessageA: {0}", message.GetType().FullName));
        }
    
        [Handles(typeof(ExampleMessageB))]
        public void HandleMessageB(ExampleMessageB message)
        {
            Console.WriteLine(string.Format("Handling message of type ExampleMessageB: {0}", message.GetType().FullName));
        }
    
        [Handles(typeof(ExampleMessageA))]
        public void HandleMessageA_WrongType(object foo)
        {
            Console.WriteLine(string.Format("HandleMessageA_WrongType: {0}", foo.GetType().FullName));
        }
    
        [Handles(typeof(ExampleMessageA))]
        public void HandleMessageA_MultipleArgs(object foo, object bar)
        {
            Console.WriteLine(string.Format("HandleMessageA_WrongType: {0}", foo.GetType().FullName));
        }
    }
    

    最后,为了把这一切结合起来:

    var components = new List<IComponent>() { new ExampleComponent() };
    var messageBroker = new MessageBroker(components);
    
    // A message has been received and deserialised into the correct type.
    // For prototyping here we will just instantiate it.
    var messageA = new ExampleMessageA();
    messageBroker.HandleMessage(messageA);
    
    var messageB = new ExampleMessageB();
    messageBroker.HandleMessage(messageB);
    

    您应该得到以下输出:

    Method HandleMessageA_WrongType does not have an IMessage as an argument
    Method HandleMessageA_MultipleArgs has too many arguments
    Handling message of type ExampleMessageA: Program+ExampleMessageA
    Handling message of type ExampleMessageB: Program+ExampleMessageB
    

    你能玩的全小提琴是 here .

    为了提高方法调用性能,可以重写 MethodInfo.Invoke 使用上述技术 here .