2012-05-03 12 views
2

私は、取るべきアクションで渡すことができる一般的な弱いイベントリスナーを開発することによって時間を節約しようとしていました。 1つが登録されなくなるまで状況はうまくいくようです。これはそれらのすべてを登録解除するようです。私が混乱しているのはなぜでしょうか、それではこれが違うのですか?こののIWeakEventListenerパラメータは?一般的なC#WeakEventリスナーが機能しないのはなぜですか?

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 
    } 

EDIT:

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

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(); 
    } 
    } 

これは主からのコードであります方法:

SomeEvent:1 SomeEvent:2 SomeEvent:3 SomeEvent:4 SomeEvent:5 SomeTimer:2 SomeTimer:3 SomeTimer:4 SomeTimer:1 SomeTimer:5
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: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なしで。

答えて

3

編集から編集:これが完成しました。私は何が起こっていると思いますか?

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

SomeClientインスタンスを削除すると、そのラムダで必要なID変数が有効範​​囲外になり、GCedになります。しかし、私は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()メソッドを使用します)。デリゲートへの参照が追加されたものを正確に指しているので、ハンドラは削除され、実行されず、エラーも出力されません。

+0

ジェネリックは私自身のテスト用です。セッターは実際には使用されず、同じ結果で削除することができます。 2つのインスタンスを作成し、1つが範囲外になると、両方が応答を停止します。 – Telavian

+0

私は使用しているコードを追加しました。 – Telavian

+0

@Telavian:編集を参照してください。 – KeithS

関連する問題