C#で匿名メソッドをサブスクライブ解除する


222

匿名メソッドをイベントから退会させることは可能ですか?

私がこのようなイベントを購読すると:

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

MyEvent += MyMethod;

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

MyEvent -= MyMethod;

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

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

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


4
なぜこれを行うことができないのかについては、stackoverflow.com
a / 25564492/23354

回答:


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

MyEvent += myDelegate;


// .... later

MyEvent -= myDelegate;

デリゲートへの参照を保持するだけです。


141

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

例:

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

1
この種のコードを使用すると、Resharperは変更されたクロージャーへのアクセスについて不平を言います...このアプローチは信頼できますか?つまり、無名メソッドの本体内の「foo」変数は、無名メソッド自体を本当に参照しているのでしょうか?
BladeWise

7
私の義務に対する答えを見つけました。それは、 'foo'が本当に匿名メソッドitslefへの参照を保持するということです。キャプチャされた変数は、匿名メソッドが割り当てられる前にキャプチャされるため、変更されます。
BladeWise、2010

2
それがまさに私が必要としたことです!= nullがありませんでした。(MyEventHandler foo = delegate {... MyEvent- = foo;}; MyEvent + = foo;動作しませんでした...)
does TDaver

Resharper 6.1は、配列として宣言しても問題ありません。少し奇妙に思われるかもしれませんが、私はこのツールで自分のツールを盲目的に信頼するつもりです。MyEventHandler [] foo = {null}; foo [0] = ... {... MyEvent-= foo [0]; }; MyEvent + = foo [0];
Mike Post

21

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

サブスクライブを解除する必要がある場合は、「通常」の方法を使用するか、デリゲートをどこかに保持して、サブスクライブに使用したのとまったく同じデリゲートでサブスクライブを解除できるようにする必要があります。


ジョン、どういう意味ですか?わかりません。「J c」によって公開されたソリューションは正しく機能しませんか?
エリックウエレット

@EricOuellet:その回答は、基本的に「デリゲートをどこかに保持して、サブスクライブに使用したのとまったく同じデリゲートでサブスクライブを解除できるようにする」の実装です。
Jon Skeet、2012年

ジョン、申し訳ありません。あなたの答えを何度も読んで、あなたが何を意味しているのか、また「J c」ソリューションが同じデリゲートをサブスクライブとサブスクライブ解除に使用していないことを理解しようとしましたが、それを理解することはできません。おそらく、あなたが言っていることを説明する記事に私を向けることができますか?私はあなたの評判を知っています。私はあなたが何を意味するのかを本当に理解したいと思っています。
Eric Ouellet、

1
私は:msdn.microsoft.com/en-us/library/ms366768.aspxを見つけましたが、匿名を使用しないことをお勧めしますが、大きな問題があるとは言われませんか?
Eric Ouellet、2018年

:私は...どうもありがとう(マイケルBlomeの答えを参照)が見つかりsocial.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/...
エリックOuellet

16

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

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

15

以来、C#7.0のローカル機能の機能がリリースされている、アプローチ提案によりJ cは本当にきちんとしたなり。

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

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


1
読みやすくするために、MyEvent + = fooを移動できます。fooの宣言の前にある行。
Mark Zhukovsky

9

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

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

私はこの技術についての完全な記事をここに書きました


2
これは、私が後でサブスクライブした場合、誤って別のインスタンス(つまり、私ではない)をイベントからサブスクライブ解除する可能性があることを意味しますか?
ダンブルダッド2014年

@dumbledadもちろん、これは常に最後に登録されたものを登録解除します。特定の匿名デリゲートを動的にサブスクライブ解除したい場合は、どうにかしてそれを識別する必要があります。そのときは参照を保持することをお勧めします:)
LuckyLikey

それはかなりクールで、あなたがやっていることですが、これが役立つ1つのケースは想像できません。しかし、私は実際にはOPの質問を解決しません。-> +1。IMHO、後で登録解除する必要がある場合は、匿名のデリゲートを使用しないでください。それらを維持するのはばかげています->メソッドを使用するのが良いでしょう。呼び出しリストから一部のデリゲートだけを削除することは、かなりランダムで無意味です。私が間違っているなら私を訂正してください。:)
LuckyLikey

6

不完全なアプローチの種類:

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. 必要に応じて、それらをすべてクリアして、他を再度追加します。

これはうまくいかないか、または最も効率的な方法かもしれませんが、仕事を完了する必要があります。


14
足りないと思ったら投稿しないでください。
ジェリーニクソン

2

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


2

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!");
}

MyEventが2回呼び出されると、実行前にMyEvent -= myEventHandlerInstance;どうなりますか?可能であれば、エラーが発生します。しかし、それが事実かどうかはわかりません。
LuckyLikey

0

このデリゲートでいくつかのオブジェクトを参照したい場合は、Delegate.CreateDelegate(Type、Object target、MethodInfo methodInfo).netを使用できる場合があります。


0

サブスクライブした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);
        }
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.