開発者が使い慣れた += 構文とインテリセンスを使用できるようにしながら、疎結合をサポートするイベント ブローカーを作成しようとしています。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);
}
}
任意のガイダンスをいただければ幸いです。