イベントハンドラーの実行順序


93

次のように複数のイベントハンドラーを設定した場合:

_webservice.RetrieveDataCompleted += ProcessData1;
_webservice.RetrieveDataCompleted += ProcessData2;

イベントRetrieveDataCompletedが発生したときにハンドラーはどの順序で実行されますか?それらは同じスレッドで実行され、登録された順序で順番に実行されますか?


2
答えはRetrieveDataCompletedイベントに固有です。マルチキャストデリゲートのデフォルトのバッキングストアがある場合は、「同じスレッドで実行され、登録された順に順次実行されます」。
HappyNomad 2013

回答:


131

現在、登録された順に実行されます。ただし、これは実装の詳細であり、仕様で要求されていないため、将来のバージョンでも同じ動作を維持することには依存しません。


5
なぜ反対票なのかしら?これはまさに真実であり、質問に直接答えます...
リードコプシー

2
@Rawling:これは、2項演算子のオーバーロードの解決用であり、イベント処理ではありません。この場合、これは加算演算子ではありません。
リードコプシー2010

2
ああ、どこが間違っているのかわかります。「イベントハンドラーはデリゲートですよね?」私は今ではそうではないことを知っています。ハンドラーを逆の順序で起動するイベントを自分で書いたが、それを自分に証明しただけだ:)
Rawling

16
明確にするために、順序は特定のイベントのバッキングストアによって異なります。イベントのデフォルトのバッキングストアであるマルチキャストデリゲートは、登録順に実行されるものとして文書化されています。これは、将来のフレームワークバージョンでは変更されません。変更される可能性があるのは、特定のイベントに使用されるバッキングストアです。
HappyNomad 2013

6
2点で事実上正しくないため、反対票を投じました。1)現在、特定のイベントの実装が指示する順序で実行されます。これは、イベントに対して独自の追加/削除メソッドを実装できるためです。2)マルチキャストデリゲートを介してデフォルトのイベント実装を使用する場合、実際には仕様で順序が必要です。
セーレンBoisen

53

デリゲートの呼び出しリストは、リストの各要素がデリゲートによって呼び出されたメソッドの1つを呼び出す順序付けられたデリゲートのセットです。呼び出しリストには、重複するメソッドを含めることができます。呼び出し中、デリゲートは、呼び出しリストに表示されている順序でメソッドを呼び出します

ここから: デリゲートクラス


1
素敵ですが、addand removeキーワードを使用すると、イベントは必ずしもマルチキャストデリゲートとして実装されない場合があります。
HappyNomad 2013

同様にボブの、他の回答は、イベントハンドラでこのの使用は信頼できないと解釈されなければならない...そうですかどうか何かであることを言及し、この答えは、あまりにも、そのことについて話すことができました。
n611x007 2014年

12

すべてのハンドラーを切り離してから、希望の順序で再接続することにより、順序を変更できます。

public event EventHandler event1;

public void ChangeHandlersOrdering()
{
    if (event1 != null)
    {
        List<EventHandler> invocationList = event1.GetInvocationList()
                                                  .OfType<EventHandler>()
                                                  .ToList();

        foreach (var handler in invocationList)
        {
            event1 -= handler;
        }

        //Change ordering now, for example in reverese order as follows
        for (int i = invocationList.Count - 1; i >= 0; i--)
        {
            event1 += invocationList[i];
        }
    }
}

10

順序は任意です。ある呼び出しから次の呼び出しまで、特定の順序で実行されるハンドラーに依存することはできません。

編集:そしてまた-これが単に好奇心から外れていない限り-あなたが知る必要があるという事実は、深刻な設計上の問題を示しています。


3
順序は特定のイベントの実装によって異なりますが、任意ではありません。ただし、イベントのドキュメントに呼び出し順序が示されていない限り、それに依存するのは危険であることに同意します。そのため、フォローアップの質問を投稿しました。
HappyNomad 2013

9
異なるクラスによって特定の順序で処理される必要があるイベントを持っていることは、私にとって深刻な設計上の問題とは思われません。この問題は、イベントの登録が順序やイベントの順序を知ることが難しくなるような方法で行われた場合に発生します。
Ignacio Soler Garcia

8

登録された順に実行されます。RetrieveDataCompletedマルチキャストデリゲートです。私はリフレクターを試して確認していますが、すべてを追跡するために舞台裏でアレイが使用されているようです。


他の回答では、イベントハンドラーを使用した場合、これは「偶発的」、「脆弱」、「実装の詳細」などになります。標準や慣例では必要ありませんが、発生します。そうですか?いずれにせよ、この回答はそれを指すこともあります。
n611x007 2014年

3

誰かがSystem.Windows.Forms.Formのコンテキストでこれを行う必要がある場合、Shownイベントの順序を逆にする例を次に示します。

using System;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Windows.Forms;

namespace ConsoleApplication {
    class Program {
        static void Main() {
            Form form;

            form = createForm();
            form.ShowDialog();

            form = createForm();
            invertShownOrder(form);
            form.ShowDialog();
        }

        static Form createForm() {
            var form = new Form();
            form.Shown += (sender, args) => { Console.WriteLine("form_Shown1"); };
            form.Shown += (sender, args) => { Console.WriteLine("form_Shown2"); };
            return form;
        }

        static void invertShownOrder(Form form) {
            var events = typeof(Form)
                .GetProperty("Events", BindingFlags.Instance | BindingFlags.NonPublic)
                .GetValue(form, null) as EventHandlerList;

            var shownEventKey = typeof(Form)
                .GetField("EVENT_SHOWN", BindingFlags.NonPublic | BindingFlags.Static)
                .GetValue(form);

            var shownEventHandler = events[shownEventKey] as EventHandler;

            if (shownEventHandler != null) {
                var invocationList = shownEventHandler
                    .GetInvocationList()
                    .OfType<EventHandler>()
                    .ToList();

                foreach (var handler in invocationList) {
                    events.RemoveHandler(shownEventKey, handler);
                }

                for (int i = invocationList.Count - 1; i >= 0; i--) {
                    events.AddHandler(shownEventKey, invocationList[i]);
                }
            }
        }
    }
}

2

MulticastDelegateには、1つ以上の要素で構成される、呼び出しリストと呼ばれるデリゲートのリンクリストがあります。マルチキャストデリゲートが呼び出されると、呼び出しリスト内のデリゲートが出現順に同期的に呼び出されます。リストの実行中にエラーが発生すると、例外がスローされます。


2

呼び出し中、メソッドは呼び出しリストに表示される順序で呼び出されます。

しかし、呼び出しリストがデリゲートを追加さ​​れたときと同じ順序で維持するとは誰も言いません。したがって、呼び出し順序は保証されません。


1

これは、マルチデリゲート呼び出しリストのどこにでも新しいイベントハンドラー関数を配置する関数です。

    private void addDelegateAt(ref YourDelegate initial, YourDelegate newHandler, int position)
    {
        Delegate[] subscribers = initial.GetInvocationList();
        Delegate[] newSubscriptions = new Delegate[subscribers.Length + 1];

        for (int i = 0; i < newSubscriptions.Length; i++)
        {
            if (i < position)
                newSubscriptions[i] = subscribers[i];
            else if (i==position)
                newSubscriptions[i] = (YourDelegate)newHandler;
            else if (i > position)
                newSubscriptions[i] = subscribers[i-1];
        }

        initial = (YourDelegate)Delegate.Combine(newSubscriptions);
    }

その後、コード内の都合の良い場所ならどこでも、「-=」を使用して関数をいつでも削除できます。

PS-'position'パラメータのエラー処理は行っていません。


0

同様の問題がありました。私の場合、それは非常に簡単に修正されました。+ =演算子を使用しないデリゲートを見たことはありません。私の問題は、1つのデリゲートが常に最後に追加され、他のすべてが常に最初に追加されることで修正されました。OPの例は次のようになります。

    _webservice.RetrieveDataCompleted = _webservice.RetrieveDataCompleted + ProcessData1;
    _webservice.RetrieveDataCompleted = ProcessData2 + _webservice.RetrieveDataCompleted;

最初のケースでは、ProcessData1が最後に呼び出されます。2番目のケースでは、ProcessData2が最初に呼び出されます。

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