2

実行するアクションを渡すだけで済む汎用の弱いイベント リスナーを開発することで、時間を節約しようとしていました。登録が解除されるまで、物事はうまくいくようです。これにより、すべての登録が解除されるようです。私が混乱しているのは、その理由と、これを IWeakEventListener パラメーターに渡すのとどう違うのですか?

  public class GenericWeakEventListener : IWeakEventListener
  {
    #region EventAction

    /// <summary>
    /// Action to take for the event
    /// </summary>
    private Action<Type, object, EventArgs> _eventAction;

    /// <summary>
    /// Gets or sets the action to take for the event
    /// </summary>
    //[DataMember]
    public Action<Type, object, EventArgs> EventAction
    {
      get
      {
        return _eventAction;
      }

      private set
      {
        if (EventAction != value)
        {
          _eventAction = value;
        }
      }
    }

    #endregion EventAction

    #region Constructors

    public GenericWeakEventListener(Action<Type, object, EventArgs> action)
    {
      EventAction = action;
    }

    #endregion Constructors

    #region Public Methods

    public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
    {
      if (EventAction != null)
      {
        EventAction(managerType, sender, e);
      }

      return true;
    }

    #endregion Public Methods
  }

編集:

これはリスナーのコードです:

  public class SomeClient
  {
    public int ID { get; set; }

    private Timer timer = null;
    private Timer timer2 = null;

    public SomeClient(int id, SomeService service)
    {
      ID = id;
      //EventHandler<GenericEventArgs<string>> d = (o, s) => Console.WriteLine("Client {0}: {1}", ID, s.Item);
      if (service != null) SomeEventChangedEventManager.AddListener(service, new GenericWeakEventListener((t, s, e) => { Console.WriteLine("SomeEvent: " + ID); }));

      timer = new Timer { AutoReset = true, Interval = 1000 };
      SomeTimerElapsedEventManager.AddListener(timer, new GenericWeakEventListener((t, s, e) => { Console.WriteLine("SomeTimer: " + ID); }));
      timer.Start();
    }
  }

これは、発行元からのコードです。

  public class SomeService
  {
    public event EventHandler<GenericEventArgs<string>> SomeEvent;

    public SomeService()
    {
      System.Timers.Timer timer = new Timer { AutoReset = true, Interval = 1000 };
      timer.Elapsed += (sender, args) => { if (SomeEvent != null) SomeEvent(this, new GenericEventArgs<string>(Guid.NewGuid().ToString())); };
      timer.Start();
    }
  }

これは、メイン メソッドのコードです。

public static void Main(string[] args)
{
  SomeService service = new SomeService();
  List<SomeClient> clients = new List<SomeClient>();

  // Build clients
  for (int x = 0; x < 5; x++)
  {
    clients.Add(new SomeClient(x + 1, service));
  }

  System.Timers.Timer timer = new Timer { AutoReset = true, Interval = 5000 };
  timer.Elapsed += (s, a) =>
    {
      if (clients.Count == 0)
      {
        return;
      }

      Console.WriteLine("Removing\r\n");
      clients.RemoveAt(0);
      GC.Collect();
    };
  timer.Start();

  Console.ReadLine();
}

これは出力です: SomeEvent: 1 SomeEvent: 2 SomeEvent: 3 SomeEvent: 4 SomeEvent: 5 SomeTimer: 2 SomeTimer: 3 SomeTimer: 4 SomeTimer: 1 SomeTimer: 5 SomeEvent: 1 SomeEvent: 2 SomeEvent: 3 SomeEvent: 4 SomeEvent: 5 SomeTimer: 1 SomeTimer: 2 SomeTimer: 3 SomeTimer: 4 SomeTimer: 5 SomeEvent: 1 SomeEvent: 2 SomeEvent: 3 SomeEvent: 4 SomeEvent: 5 SomeTimer: 2 SomeTimer: 3 SomeTimer: 4 SomeTimer: 5 SomeTimer: 1 SomeEvent: 1 SomeEvent: 2 SomeEvent: 3 SomeEvent: 4 SomeEvent: 5 SomeTimer: 1 SomeTimer: 2 SomeTimer: 3 SomeTimer: 5 SomeTimer: 4 SomeEvent: 1 SomeEvent: 2 SomeEvent: 3 SomeEvent: 4 SomeEvent: 5

一般的な弱いイベント リスナーがないと、出力は 1 なしで続行され、次に 2 なしで続きます。

4

1 に答える 1

3

OP EDIT からの編集:わかりました。これが私が起こっていると思うことです:

SomeClient が所有し、インスタンス変数 ID への外部クロージャーを含むラムダを宣言しています。これにより、SomeEventChangedEventManager を介して SomeService のイベントにデリゲートとして渡されるラムダが、その ID のインスタンスの継続的な存在に依存します。

その SomeClient インスタンスを削除すると、そのラムダに必要な ID 変数がスコープ外になり、GC されます。ただし、このラムダを SomeService の SomeEvent のハンドラーから削除するコードの部分は見当たりません。そのため、ラムダは匿名デリゲートへの参照としてメモリに残りますが、依存する他のデータは失われます。これにより、ランタイムによって例外がスローされますが、何らかの形で飲み込まれ、プログラム全体が爆発することはありません。

ただし、基本的にハンドラー デリゲートをアタッチされた順序で実行するイベント (これは一般的に無視するように指示される実装の詳細です) は、ハンドラーの 1 つがスローされたため、実行を停止しました。これにより、最初のクライアントを削除すると、それらすべてが削除されたように見えますが、実際には、最初のハンドラーがエラーを起こしたためにそれらのクライアントのハンドラーが実行されていません。

修正は 2 つあります。

  • ラムダを定義し、それを SomeClient のインスタンス変数として保存します。これにより、それへの参照を保持できます。これは重要です。デリゲートは同等性を判断するときに意味的に比較されないため、次のコードは機能しません。

    SomeEvent += (a,b,c) => Foo(a,b,c);
    //the following line will not remove the handler added in the previous line,
    //because the two lambdas are compiled into differently-named methods
    //and so this is a different reference to a different method.
    SomeEvent -= (a,b,c) => Foo(a,b,c);
    
  • IDisposable やファイナライザーを SomeClient に実装します。リストからクライアントを削除する場合、GC によって呼び出されるディスポーザー/ファイナライザーは、このインスタンスのラムダを SomeEvent リスナーから削除する必要があります (おそらく Manager の RemoveListener() メソッドを介して)。追加されたものを正確に指すデリゲートへの参照を保持しているため、ハンドラーは削除され、実行されず、エラーも発生しません。

于 2012-05-03T16:07:45.883 に答える