Это возможно, и только немного сложнее.Вам нужно будет выполнить поиск в ваших компонентах методов, имеющих атрибут 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
Полная скрипка, с которой вы можете играть: здесь .
Чтобы улучшить производительность вызова метода, вы можете переписать MethodInfo.Invoke
, используя упомянутые методы здесь .