我正在尝试创建一个支持松散耦合的事件代理,同时仍允许开发人员使用熟悉的 += 语法和智能感知。我正在努力解决如何在不使用 DynamicInvoke 的情况下一般调用代表。
大概的概念:
所有事件都在接口中定义,每个事件都采用一个派生自 EventInfoBase 的参数。
public delegate void EventDelegate<in T>(T eventInfo) where T : EventInfoBase;
在订阅方,客户端向 Windsor 容器请求实现事件接口的实例。Windsor 返回一个拦截所有 += (add_xxx) 和 -= (remove_xxx) 调用的代理。存储类型和委托信息以供将来在触发事件时查找和执行。目前,委托存储为委托,但更愿意将其存储为 EventDelegate 之类的东西。
在发布者端,发布者向事件代理注册作为事件接口的源。事件代理通过反射使用 EventDelegate<EventInfoBase> 类型的委托订阅每个事件。
当一个事件被触发时,事件代理会查找任何合适的委托并执行它们。
问题:
使用 EventInfoBase 基类向发布者添加事件处理程序是逆变的合法使用。但事件代理无法将客户端订阅存储为 EventDelegate<EventInfoBase>。Eric Lippert 在这篇博文中解释了原因。
关于如何存储客户端订阅(委托)以便以后可以在不使用 DynamicInvoke 的情况下调用它们的任何想法?
更新了其他详细信息:
订阅者向事件代理请求事件接口,然后根据需要订阅事件。
// a simple event interface
public class EventOneArgs : EventInfoBase { }
public class EventTwoArgs : EventInfoBase { }
public interface ISomeEvents
{
event EventDelegate<EventOneArgs> EventOne;
event EventDelegate<EventTwoArgs> EventTwo;
}
// client gets the event broker and requests the interface
// to the client it looks like a typical object with intellisense available
IWindsorContainer cont = BuildContainer();
var eb = cont.Resolve<IEventBroker>();
var service = eb.Request<ISomeEvents>();
service.EventOne += new EventDelegate<EventOneArgs>(service_EventOne);
service.EventTwo += new EventDelegate<EventTwoArgs>(service_EventTwo);
在后台,事件代理对事件接口一无所知,它返回该接口的代理。所有 += 调用都被拦截,订阅添加了代表字典。
public T Request<T>(string name = null) where T : class
{
ProxyGenerator proxygenerator = new ProxyGenerator();
return proxygenerator.CreateInterfaceProxyWithoutTarget(typeof(T),
new EventSubscriptionInterceptor(this, name)) as T;
}
public void Intercept(IInvocation invocation)
{
if (invocation.Method.IsSpecialName)
{
if (invocation.Method.Name.Substring(0, s_SubscribePrefix.Length) == s_SubscribePrefix) // "add_"
{
// DeclaringType.FullName will be the interface type
// Combined with the Name - prefix, it will uniquely define the event in the interface
string uniqueName = invocation.Method.DeclaringType.FullName + "." + invocation.Method.Name.Substring(s_SubscribePrefix.Length);
var @delegate = invocation.Arguments[0] as Delegate;
SubscirptionMgr.Subscribe(uniqueName, @delegate);
return;
}
// ...
}
}
存储在 SubscriptionManager 中的委托属于 EventDelegate<T> 类型,其中 T 是事件定义的派生类型。
发布者:发布者注册为事件接口的来源。目标是消除显式调用事件代理的需要,并允许使用 EventName(args) 的典型 C# 语法。
public class SomeEventsImpl : ISomeEvents
{
#region ISomeEvents Members
public event Ase.EventBroker.EventDelegate<EventOneArgs> EventOne;
public event Ase.EventBroker.EventDelegate<EventTwoArgs> EventTwo;
#endregion
public SomeEventsImpl(Ase.EventBroker.IEventBroker eventBroker)
{
// register as a source of events
eventBroker.RegisterSource<ISomeEvents, SomeEventsImpl>(this);
}
public void Fire_EventOne()
{
if (EventOne != null)
{
EventOne(new EventOneArgs());
}
}
}
事件代理使用反射来订阅具有公共处理程序的接口中的所有事件(AddEventHandler)。我还没有尝试过组合处理程序。我创建了一个包装类,以防在触发事件时需要可用的其他信息,例如类型。
public void RegisterSource<T, U>(U instance)
where T : class
where U : class
{
T instanceAsEvents = instance as T;
string eventInterfaceName = typeof(T).FullName;
foreach (var eventInfo in instanceAsEvents.GetType().GetEvents())
{
var wrapper = new PublishedEventWrapper(this, eventInterfaceName + "." + eventInfo.Name);
eventInfo.AddEventHandler(instance, wrapper.EventHandler);
}
}
class PublishedEventWrapper
{
private IEventPublisher m_publisher = null;
private readonly EventDelegate<EventInfoBase> m_handler;
private void EventInfoBaseHandler(EventInfoBase args)
{
if (m_publisher != null)
{
m_publisher.Publish(this, args);
}
}
public PublishedEventWrapper(IEventPublisher publisher, string eventName)
{
m_publisher = publisher;
EventName = eventName;
m_handler = new EventDelegate<EventInfoBase>(EventInfoBaseHandler);
}
public string EventName { get; private set; }
public EventDelegate<EventInfoBase> EventHandler
{
get { return m_handler; }
}
}
我一直在努力解决的问题在于发布。Publish 方法查找事件的委托并需要执行它们。由于 DynamicInvoke 的性能问题,我想将委托转换为正确的 EventDelegate<T> 表单并直接调用它,但还没有找到解决方法。
为了弄清楚这一点,我当然学到了很多东西,但是时间不多了。我可能缺少一些简单的东西。我尝试将委托包装在另一个(动态生成的)中,基本上看起来像:
private static void WrapDelegate(Delegate d, DerivedInfo args)
{
var t = d as EventDelegate<DerivedInfo>;
if (t != null)
{
t(args);
}
}
任何指导将不胜感激。