web-dev-qa-db-ja.com

C#での匿名メソッドの購読解除

イベントから匿名メソッドをサブスクライブ解除することは可能ですか?

このようなイベントを購読する場合:

void MyMethod()
{
    Console.WriteLine("I did it!");
}

MyEvent += MyMethod;

私はこのように退会することができます:

MyEvent -= MyMethod;

しかし、匿名メソッドを使用してサブスクライブする場合:

MyEvent += delegate(){Console.WriteLine("I did it!");};

この匿名メソッドの購読を解除することは可能ですか?もしそうなら、どのように?

212
Eric
Action myDelegate = delegate(){Console.WriteLine("I did it!");};

MyEvent += myDelegate;


// .... later

MyEvent -= myDelegate;

デリゲートへの参照を保持してください。

219
Jacob Krall

1つの手法は、匿名メソッドを保持する変数を宣言して、匿名メソッド自体の内部で使用できるようにすることです。イベントが処理された後に希望する動作がサブスクライブを解除することだったので、これは私にとってはうまくいきました。

例:

MyEventHandler foo = null;
foo = delegate(object s, MyEventArgs ev)
    {
        Console.WriteLine("I did it!");
        MyEvent -= foo;
    };
MyEvent += foo;
138
J c

メモリから、仕様は匿名メソッドで作成されたデリゲートの等価性に関して、どちらの方法でも動作を明示的に保証しません。

サブスクライブを解除する必要がある場合は、「通常の」メソッドを使用するか、デリゲートを別の場所に保持して、サブスクライブに使用したのとまったく同じデリゲートでサブスクライブを解除する必要があります。

21
Jon Skeet

3.0では、次のように短縮できます。

MyHandler myDelegate = ()=>Console.WriteLine("I did it!");
MyEvent += myDelegate;
...
MyEvent -= myDelegate;
17
yaniv

デリゲートへの参照を保持する代わりに、イベントの呼び出しリストを呼び出し元に返すためにクラスをインスツルメントできます。基本的に、次のようなものを書くことができます(MyClass内でMyEventが宣言されていると仮定します):

public class MyClass 
{
  public event EventHandler MyEvent;

  public IEnumerable<EventHandler> GetMyEventHandlers()  
  {  
      return from d in MyEvent.GetInvocationList()  
             select (EventHandler)d;  
  }  
}

そのため、MyClassの外部から呼び出しリスト全体にアクセスし、必要なハンドラーをサブスクライブ解除できます。例えば:

myClass.MyEvent -= myClass.GetMyEventHandlers().Last();

このテクニックについての完全な投稿を書いた here

9
hemme

C#7.0 local functions 機能がリリースされたため、 J c による 推奨 のアプローチは本当にすてきになります。

void foo(object s, MyEventArgs ev)
{
    Console.WriteLine("I did it!");
    MyEvent -= foo;
};
MyEvent += foo;

したがって、正直なところ、ここには変数としての匿名関数はありません。しかし、あなたのケースでそれを使用する動機はローカル関数に適用できると思います。

7
mazharenko

ラメアプローチの種類:

public class SomeClass
{
  private readonly IList<Action> _eventList = new List<Action>();

  ...

  public event Action OnDoSomething
  {
    add {
      _eventList.Add(value);
    }
    remove {
      _eventList.Remove(value);
    }
  }
}
  1. イベントの追加/削除メソッドをオーバーライドします。
  2. それらのイベントハンドラのリストを保持します。
  3. 必要に応じて、それらをすべてクリアし、他を再度追加します。

これは機能しないか、最も効率的な方法かもしれませんが、仕事を終わらせる必要があります。

6
casademora

1つの簡単なソリューション:

eventhandle変数をパラメーターとしてそれ自体に渡すだけです。マルチスレッドのために作成された元の変数にアクセスできない場合がある場合、これを使用できます。

MyEventHandler foo = null;
foo = (s, ev, mehi) => MyMethod(s, ev, foo);
MyEvent += foo;

void MyMethod(object s, MyEventArgs ev, MyEventHandler myEventHandlerInstance)
{
    MyEvent -= myEventHandlerInstance;
    Console.WriteLine("I did it!");
}
2
Manuel Marhold

退会を制御できるようにするには、受け入れられた回答に示されているルートに進む必要があります。ただし、サブスクライブするクラスが範囲外になったときに参照をクリアすることだけを懸念している場合は、弱い参照の使用を伴う別の(わずかに複雑な)ソリューションがあります。このトピックについて 質問と回答 を投稿しました。

2
Benjol

最良の方法がサブスクライブされたeventHandlerの参照を保持することである場合、これは辞書を使用して実現できます。

この例では、匿名メソッドを使用して、DataGridViewのセットのmergeColumnパラメーターを含める必要があります。

Enableパラメーターをtrueに設定してMergeColumnメソッドを使用すると、イベントが有効になり、falseで使用すると無効になります。

static Dictionary<DataGridView, PaintEventHandler> subscriptions = new Dictionary<DataGridView, PaintEventHandler>();

public static void MergeColumns(this DataGridView dg, bool enable, params ColumnGroup[] mergedColumns) {

    if(enable) {
        subscriptions[dg] = (s, e) => Dg_Paint(s, e, mergedColumns);
        dg.Paint += subscriptions[dg];
    }
    else {
        if(subscriptions.ContainsKey(dg)) {
            dg.Paint -= subscriptions[dg];
            subscriptions.Remove(dg);
        }
    }
}
0
Larry

このデリゲートでオブジェクトを参照する場合は、Delegate.CreateDelegate(Type、Object target、MethodInfo methodInfo)を使用できます。

0
user3217549