24

次のコードがあります。

public List<IWFResourceInstance> FindStepsByType(IWFResource res)  
{  
    List<IWFResourceInstance> retval = new List<IWFResourceInstance>();  
    this.FoundStep += delegate(object sender, WalkerStepEventArgs e)   
                      {   
                        if (e.Step.ResourceType == res) retval.Add(e.Step);   
                      };  
    this.Start();  
    return retval;
}  

イベント メンバー (FoundStep) をローカルのインプレース匿名関数に登録する方法に注目してください。

私の質問は次のとおりです: 関数 'FindStepByType' が終了するとき - 匿名関数はイベントのデリゲート リストから自動的に削除されますか、または関数をステップアウトする前に手動で削除する必要がありますか? (そして、どうすればそれを行うことができますか?)

私の質問が明確だったことを願っています。

4

4 に答える 4

45

あなたのコードにはいくつかの問題があります (あなたと他の人が特定したものもあります):

  • コード化されているように、匿名のデリゲートをイベントから削除することはできません。
  • 匿名デリゲートは、 thisのメンバーであるFoundStepに追加したため、それを呼び出すメソッドの寿命よりも長く存続します。
  • FindStepsByType へのエントリごとに、別の匿名デリゲートがFoundStepに追加されます
  • 匿名デリゲートはクロージャであり、実質的にretvalの有効期間を延長するため、コード内の他の場所でretvalの参照を停止した場合でも、匿名デリゲートによって引き続き保持されます。

これを修正し、引き続き匿名デリゲートを使用するには、それをローカル変数に割り当ててから、finallyブロック内のハンドラーを削除します (ハンドラーが例外をスローする場合に必要です)。

  public List<IWFResourceInstance> FindStepsByType(IWFResource res)
  {
     List<IWFResourceInstance> retval = new List<IWFResourceInstance>();
     EventHandler<WalkerStepEventArgs> handler = (sender, e) =>
     {
        if (e.Step.ResourceType == res) retval.Add(e.Step);
     };

     this.FoundStep += handler;

     try
     {
        this.Start();
     }
     finally
     {
        this.FoundStep -= handler;
     }

     return retval;
  }

C# 7.0 以降では、匿名デリゲートをローカル関数に置き換えて、同じ効果を得ることができます。

    public List<IWFResourceInstance> FindStepsByType(IWFResource res)
    {
        var retval = new List<IWFResourceInstance>();

        void Handler(object sender, WalkerStepEventArgs e)
        {
            if (e.Step.ResourceType == res) retval.Add(e.Step);
        }

        FoundStep += Handler;

        try
        {
            this.Start();
        }
        finally
        {
            FoundStep -= Handler;
        }

        return retval;
    }
于 2009-09-07T16:55:41.777 に答える
11

以下は、匿名メソッドでイベントを購読解除する方法に関するアプローチです。

DispatcherTimer _timer = new DispatcherTimer();
_timer.Interval = TimeSpan.FromMilliseconds(1000);
EventHandler handler = null;

int i = 0;

_timer.Tick += handler = new EventHandler(delegate(object s, EventArgs ev)
{
    i++;
    if(i==10)
        _timer.Tick -= handler;
});

_timer.Start();
于 2010-10-03T10:15:23.750 に答える
5

いいえ、自動的には削除されません。この意味では、匿名メソッドと「通常の」メソッドの間に違いはありません。必要に応じて、手動でイベントの登録を解除する必要があります。

実際には、他の変数 (たとえばres、あなたの例) をキャプチャし、それらを存続させます (ガベージ コレクターがそれらを収集するのを防ぎます)。

于 2009-09-07T13:57:17.930 に答える
2

匿名デリゲート(またはラムダ式)を使用してイベントをサブスクライブする場合、後でそのイベントから簡単にサブスクライブを解除することはできません。イベントハンドラーが自動的にサブスクライブ解除されることはありません。

コードを見ると、関数でイベントを宣言してサブスクライブしていても、サブスクライブしているイベントはクラス上にあるため、サブスクライブすると、関数が終了した後も常にサブスクライブされます。認識しておくべきもう1つの重要なことは、この関数が呼び出されるたびに、イベントを再度サブスクライブすることです。イベントは基本的にマルチキャストデリゲートであり、複数のサブスクライバーを許可するため、これは完全に合法です。(これはあなたが意図したものであるかもしれないし、そうでないかもしれません。)

関数を終了する前にデリゲートのサブスクライブを解除するには、匿名デリゲートをデリゲート変数に格納し、デリゲートをイベントに追加する必要があります。これで、関数が終了する前に、イベントからデリゲートを削除できるようになります。

これらの理由から、後でイベントの購読を解除する必要がある場合は、匿名の代理人を使用することはお勧めしません。「方法:イベントの購読と購読解除(C#プログラミングガイド)」(具体的には、「匿名メソッドを使用してイベントを購読するには」というタイトルのセクション)を参照してください。

于 2009-09-07T14:57:32.177 に答える