如何将消息反序列化为强类型 object,然后为该消息动态调用 runtime-assigned 处理程序
How to deserialize a message into a strongly typed object, then dynamically call a runtime-assigned handler for that message
呸,多么好的标题...
我正在为一个学习项目开发服务器。我花了很多时间试图弄清楚如何正确地提出这个问题。一开始,我想我什至不知道我想要达到的目标。
我正在使用的服务器有 N 个组件(N >= 0)。
- 每个组件都是在运行时使用 DI 添加的。
- 每个组件都是一个"black box"。当它收到一条消息时,它被认为是 "unit of work" 并且拥有从头到尾所需的一切。
- 每个组件负责将
subscribing
的信息提供给 message
,并为该消息提供 handler
。我计划使用 handler
函数的属性来实现此目的。
一个"handler"的例子:
[Handles(ExampleMessage)]
private void handleExampleMessage(ExampleMessage message)
{
DoStuff();
}
这是我能想到的最清晰的问题构建方式:
如何实现 typed "message broker" 系统,就像 ASP.NET MVC 如何为controller
action
来自 序列化 输入。
所以我想要实现的是:
Serialized message
-> Strongly typed message
-> message service
-> call handler function with *strongly typed* message as an argument
我想到了几点:
我尝试的第一件事就是 de-serializing 向 dynamic
发送消息,但是没有 inellisense 并且 compile-time 检查的成本太高 dynamic
对我来说。
然后我尝试在运行时使用反射创建静态 de-serialization 方法,并使用这些方法中的 return 值来调用 "handlers",但它得到 so丑和意大利面,我不得不放弃它(尽管如果有人有优雅、注重性能的方式,我当然仍然接受这个选项)
最后我尝试使用所有消息继承的 Message
类型,但是当我尝试使用 Dictionary<Action<Message>, Message>
调用适当的处理程序时我最终卡住了
这是可能的,只是有点复杂。您要做的是在组件中搜索具有 Handles
属性的方法,并通过反射调用它们。
假设我们有以下接口:
public interface IComponent
{
}
public interface IMessage
{
};
我们还创建 Handles
属性,让我们将方法标记为处理特定消息类型:
[AttributeUsage(AttributeTargets.Method)]
public class HandlesAttribute : Attribute
{
public Type MessageType { get; private set; }
public HandlesAttribute(Type messageType)
{
MessageType = messageType;
}
};
现在我们将创建一个消息代理,它将负责在给定的组件列表中查找所有消息处理方法。我们将使用反射来做到这一点。首先,我们将找到所有具有 Handles
属性的方法,然后我们将检查它们是否具有所需的单个 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);
}
}
上面的构造函数记录一条警告消息并忽略任何与我们需要的签名不匹配的方法(即从 IMessage 派生的一个参数)。
现在让我们添加一个方法来处理消息。这将使用 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
您可以玩的完整 fiddle 是 here。
要提高方法调用性能,您可以使用 here.
中提到的技术重写 MethodInfo.Invoke
呸,多么好的标题...
我正在为一个学习项目开发服务器。我花了很多时间试图弄清楚如何正确地提出这个问题。一开始,我想我什至不知道我想要达到的目标。
我正在使用的服务器有 N 个组件(N >= 0)。
- 每个组件都是在运行时使用 DI 添加的。
- 每个组件都是一个"black box"。当它收到一条消息时,它被认为是 "unit of work" 并且拥有从头到尾所需的一切。
- 每个组件负责将
subscribing
的信息提供给message
,并为该消息提供handler
。我计划使用handler
函数的属性来实现此目的。
一个"handler"的例子:
[Handles(ExampleMessage)]
private void handleExampleMessage(ExampleMessage message)
{
DoStuff();
}
这是我能想到的最清晰的问题构建方式:
如何实现 typed "message broker" 系统,就像 ASP.NET MVC 如何为controller
action
来自 序列化 输入。
所以我想要实现的是:
Serialized message
-> Strongly typed message
-> message service
-> call handler function with *strongly typed* message as an argument
我想到了几点:
我尝试的第一件事就是 de-serializing 向 dynamic
发送消息,但是没有 inellisense 并且 compile-time 检查的成本太高 dynamic
对我来说。
然后我尝试在运行时使用反射创建静态 de-serialization 方法,并使用这些方法中的 return 值来调用 "handlers",但它得到 so丑和意大利面,我不得不放弃它(尽管如果有人有优雅、注重性能的方式,我当然仍然接受这个选项)
最后我尝试使用所有消息继承的 Message
类型,但是当我尝试使用 Dictionary<Action<Message>, Message>
调用适当的处理程序时我最终卡住了
这是可能的,只是有点复杂。您要做的是在组件中搜索具有 Handles
属性的方法,并通过反射调用它们。
假设我们有以下接口:
public interface IComponent
{
}
public interface IMessage
{
};
我们还创建 Handles
属性,让我们将方法标记为处理特定消息类型:
[AttributeUsage(AttributeTargets.Method)]
public class HandlesAttribute : Attribute
{
public Type MessageType { get; private set; }
public HandlesAttribute(Type messageType)
{
MessageType = messageType;
}
};
现在我们将创建一个消息代理,它将负责在给定的组件列表中查找所有消息处理方法。我们将使用反射来做到这一点。首先,我们将找到所有具有 Handles
属性的方法,然后我们将检查它们是否具有所需的单个 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);
}
}
上面的构造函数记录一条警告消息并忽略任何与我们需要的签名不匹配的方法(即从 IMessage 派生的一个参数)。
现在让我们添加一个方法来处理消息。这将使用 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
您可以玩的完整 fiddle 是 here。
要提高方法调用性能,您可以使用 here.
中提到的技术重写MethodInfo.Invoke