6

最近、C# 拡張メソッドを使用してイベントの呼び出しを簡単にする方法を学び、ますます使用しています。私は最近、理解できない奇妙な問題にぶつかりました.誰かがそれを説明できるかどうか疑問に思っていました.

この問題は、イベント ハンドラ拡張メソッドを別のイベントのイベント ハンドラとして設定しようとすると発生します。これが私がやっていることの例です:

public static class EventHandlerExtensions
{
    public static void Raise<TEventArgs>(
        this EventHandler<TEventArgs> eventHandler, 
        object sender, TEventArgs args)  where TEventArgs:EventArgs
    {
        if (eventHandler != null)
        {
            eventHandler(sender, args);
        }
    }
}

public class Test
{
    private event EventHandler<EventArgs> EventA;
    private event EventHandler<EventArgs> EventB;

    public Test()
    {
        Console.WriteLine("::Start");
        EventB += EventA.Raise;
        EventA += (s, a) => Console.WriteLine("Event A raised");
        EventB.Raise(this, EventArgs.Empty);
        Console.WriteLine("::End");
    }
}

この例では、EventB がトリガーされた結果として、EventA がトリガーされます。ただし、このコードを実行すると、EventB が発生しますが、A の拡張メソッドはそのリスナーを見つけられません。

順序を変更すると、すべて正常に動作します。

Console.WriteLine("::Start");
EventA += (s, a) => Console.WriteLine("Event A raised");
EventB += EventA.Raise;
EventB.Raise(this, EventArgs.Empty);
Console.WriteLine("::End");

また、ラムダから EventA.Raise を呼び出しても問題なく動作します。

Console.WriteLine("::Start");
EventB += (s, a) => EventA.Raise(s, a);
EventA += (s, a) => Console.WriteLine("Event A raised");
EventB.Raise(this, EventArgs.Empty);
Console.WriteLine("::End");

これは単純な例ですが、追加されたイベント ソースのイベントを可能な限りクリーンな方法で再ディスパッチできるクラスを作成しようとしています。同じイベントを再ディスパッチするためだけに名前付きメソッドを作成したくありません。また、後でイベント ハンドラーからフック解除できるラムダ関数のリストを保存したくありません。ほとんどの場合、なぜこれが起こっているのか興味がありますか?

何か案は?

4

2 に答える 2

4

Raise 関数によって EventA の古い値をクロージャーにキャプチャします。後で += を使用すると EventA の値が変更されますが、クロージャーにはまだ古い値があります。

コード:

EventB += EventA.Raise;
EventA += (s, a) => Console.WriteLine("Event A raised"); 

古いデリゲートを取得する理由を明確にする同等のコードに展開できます。

var oldEventA = EventA;
EventB += oldEventA.Raise; // captures old value here
// now EventA changed to new value 
EventA = oldEventA + ((s, a) => Console.WriteLine("Event A raised");)

前に以下を追加しEventB += EventA.Raiseて、コードが実際に A の古いイベントを発生させることを確認できます。

EventA += (s, a) => Console.WriteLine("Old Event A raised");
于 2012-05-24T01:54:44.970 に答える
3

デリゲート オブジェクトは不変です。文字列によく似ています。したがって、EventA を割り当てると、新しいオブジェクトが作成されます。EventB は、まだイベント ハンドラーが割り当てられていない古いものをターゲットにしています。問題を解決するには、2 つのステートメントを交換する必要があります。

于 2012-05-24T02:16:07.580 に答える