3

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

任意のガイダンスをいただければ幸いです。

4

2 に答える 2

2

DynamicInvoke を使用せずに後で呼び出すことができるように、クライアント サブスクリプション (デリゲート) を保存する方法についてのアイデアはありますか?

を使用Dictionary<Type, Delegate>して、適切にキャストできます。

public void Subscribe<T>(EventDelegate<T> handler) where T : EventInfoBase
{
    Delegate existingHandlerPlain;
    // We don't actually care about the return value here...
    dictionary.TryGetValue(typeof(T), out existingHandlerPlain);
    EventDelegate<T> existingHandler = (EventDelegate<T>) existingHandlerPlain;
    EventDelegate<T> newHandler = existingHandler + handler;
    dictionary[typeof(T)] = newHandler;
}

public void Publish<T>(EventInfo<T> info) where T : EventInfoBase
{
    Delegate handlerPlain;
    if (dictionary.TryGetValue(typeof(T), out handlerPlain))
    {
        EventDelegate<T> handler = (EventDelegate<T>) handlerPlain;
        handler(info);
    }
}

自分でコンテンツを管理しているため、キャストは常に安全でなければなりません。

ただし、実際には異なるタイプのイベントハンドラーを組み合わせようとすると、分散の問題が発生する可能性があります。List<EventHandler<T>>それが問題になる場合は、「通常の」結合操作を使用する代わりに、明示的に a を使用する必要があります。

于 2012-06-20T16:54:46.023 に答える
1

デリゲートをラップする解決策:

ジェネリック デリゲートを既知の型のデリゲートでラップする方法を見つけることができました。これにより、 SomeDelegate(args) の標準呼び出し規約を使用できます。このソリューションは、次によって定義された汎用デリゲートを取り込みます。

public delegate void EventDelegate<in T>(T eventInfo) where T : EventInfoBase;

ジェネリック デリゲートは、インフラストラクチャ コードから呼び出すことができる既知のシグネチャを持つデリゲートによってラップされます。このアプローチのパフォーマンスはまだ確認していません。MethodInfo.Invoke を呼び出すコストは、イベントへのサブスクリプションで発生します。イベントを発生させるコストは、DelegateWrapper のコードです。

  private static EventDelegate<EventInfoBase> DelegateWrapper<T>(Delegate @delegate) where T : EventInfoBase
  {
     return (o =>
        {
           var t = @delegate as EventDelegate<T>;
           var args = o as T;
           if (t != null && o != null)
           {
              t(args);
           }
        }
     );
  }

  private static readonly MethodInfo s_eventMethodInfo = typeof(EventSubscriptionInterceptor).GetMethod("DelegateWrapper", BindingFlags.NonPublic | BindingFlags.Static);

  private EventDelegate<EventInfoBase> GenerateDelegate(Delegate d)
  {
     MethodInfo closedMethod = s_eventMethodInfo.MakeGenericMethod(d.Method.GetParameters()[0].ParameterType);
     var newDel = closedMethod.Invoke(null, new object[] { d }) as EventDelegate<EventInfoBase>;
     return newDel;
  }

少なくとも、これにより、「ほぼ」C# 構文を使用した疎結合イベント ブローカーの実用的なプロトタイプが得られます。

于 2012-06-27T18:44:44.363 に答える