イベントAction <>とイベントEventHandler <>


144

宣言の間に何が違うのがあるevent Action<>とはevent EventHandler<>

どのオブジェクトが実際にイベントを発生させたかは関係ないと仮定します。

例えば:

public event Action<bool, int, Blah> DiagnosticsEvent;

public event EventHandler<DiagnosticsArgs> DiagnosticsEvent;

class DiagnosticsArgs : EventArgs
{
    public DiagnosticsArgs(bool b, int i, Blah bl)
    {...}
    ...
}

使用方法はどちらの場合もほぼ同じです。

obj.DiagnosticsEvent += HandleDiagnosticsEvent;

event EventHandler<>パターンについて私が気に入らない点がいくつかあります。

  • EventArgsから派生した追加の型宣言
  • オブジェクトソースの強制的な通過-多くの場合、誰も気にしません

コードが多いほど、明確な利点なしに維持するコードが多くなります。

その結果、私は好む event Action<>

ただし、Action <>に型引数が多すぎる場合にのみ、追加のクラスが必要になります。


2
「誰も気にしない」ためのplusOne(私はシステムを破っただけです)
hyankov '28年

@plusOne:私は実際に送信者を知る必要があります!何かが起こったとしたら、誰がそれをしたのか知りたいとしましょう。それはあなたが「オブジェクトソース」(別名送信者)が必要だったということです。
Kamran Bigdely

送信者はイベントのペイロードのプロパティである可能性があります
Thanasis Ioannidis

回答:


67

主な違いはAction<>、イベントを使用すると、システム内の他のイベントの設計パターンに実質的に従わないことです。これは、欠点と考えられます。

支配的なデザインパターンの利点の1つは(同一性の力は別として)EventArgs、イベントのシグネチャを変更せずに、オブジェクトを新しいプロパティで拡張できることです。これは、を使用した場合でも可能ですAction<SomeClassWithProperties>が、その場合、通常のアプローチを使用しないことのポイントは実際にはわかりません。


使用Action<>するとメモリリークが発生する可能性がありますか?EventHandler設計パターンの1つの欠点は、メモリリークです。また、複数のイベントハンドラがあり得ることを指摘しなければならないが、一つだけアクション
ルークTオブライエン

4
@ LukeTO'Brien:イベントは本質的にデリゲートであるため、と同じメモリリークの可能性がありAction<T>ます。また、an Action<T> いくつかのメソッドを参照できます。ここではそれを証明する要点は次のとおりです。gist.github.com/fmork/4a4ddf687fa8398d19ddb2df96f0b434
フレドリックモルク

88

以前の回答のいくつかに基づいて、私の回答を3つの領域に分類します。

まず、のAction<T1, T2, T2... >派生クラスの使用と使用の物理的な制限EventArgs。3つあります。最初に、パラメーターの数またはタイプを変更する場合、サブスクライブするすべてのメソッドを変更して、新しいパターンに準拠する必要があります。これがサードパーティアセンブリが使用する公開イベントであり、イベント引数が変更される可能性がある場合、これが一貫性のためにイベント引数から派生したカスタムクラスを使用する理由になります(覚えておいてください)使用Action<MyCustomClass>使用して、)第二にAction<T1, T2, T2... >、あなたがアクションと一緒に渡されたインスタンスのHandledプロパティを持つオブジェクトのいくつかの種類を()がない限り、メソッド呼び出しにフィードバックBACKを渡すことを防止することができます。第三に、あなたは3を渡しているのであれば、パラメータの名前を取得していないbool「S int、2stringの、そしてDateTime、あなたはそれらの値の意味が何であるかを知りません。余談ですが、「まだ使用中にこのイベントを安全に起動する」メソッドを使用できAction<T1, T2, T2... >ます。

第二に、一貫性の影響。大規模なシステムをすでに使用している場合は、非常に正当な理由がない限り、ほとんどの場合、システムの残りの部分の設計方法に従うことをお勧めします。維持する必要がある公的に直面するイベントがある場合、派生クラスを置き換える機能が重要になる可能性があります。心に留めておきます。

3番目に、実際の練習では、対話する必要があるプロパティの変更(特に、相互に対話するビューモデルを使用してMVVMを実行する場合)またはイベントが発生する場所について、1回限りのイベントを多数作成する傾向があることに個人的に気づきます単一のパラメータ。時間のほとんどは、これらのイベントは次の形式を取りますpublic event Action<[classtype], bool> [PropertyName]Changed;public event Action SomethingHappened;。これらの場合、2つの利点があります。最初に、発行クラスのタイプを取得します。をMyClass宣言し、それがイベントを発生させる唯一のクラスMyClassである場合、イベントハンドラで処理するの明示的なインスタンスを取得します。次に、プロパティ変更イベントなどの単純なイベントの場合、パラメーターの意味は明白であり、イベントハンドラーの名前に示され、これらの種類のイベントのために無数のクラスを作成する必要はありません。


素晴らしいブログ投稿。あなたがこのスレッドを読んでいるなら、間違いなく読む価値があります!
Vexir 2014年

1
結論の背後にある理由を説明する詳細でよく考えられた回答
MikeT 2015

18

ほとんどの場合、私はパターンに従っていると思います。私それから逸脱しましたが、非常にまれであり、特定の理由のためです。この場合、私が持っている最大の問題は、おそらくまだを使用していてAction<SomeObjectType>、後で追加のプロパティを追加し、時折の双方向のプロパティを使用できることです(Handledまたは、サブスクライバーは、イベントオブジェクトのプロパティを設定する必要があります)。そして、いったんその一歩を踏み出したらEventHandler<T>、いくつかのに使うこともできTます。


14

ワード数の多いアプローチの利点は、コードが300,000行のプロジェクト内にある場合です。

あなたが持っているように、アクションを使用して、bool、int、Blahが何であるかを私に伝える方法はありません。アクションがパラメーターを定義したオブジェクトを渡した場合は、OKです。

EventArgsを必要とするEventHandlerを使用し、DiagnosticsArgsの例に、目的をコメント化したプロパティのゲッターを追加すると、アプリケーションがより理解しやすくなります。また、DiagnosticsArgsコンストラクターの引数をコメント化するか、完全な名前を付けてください。


6

標準のイベントパターンに従う場合は、拡張メソッドを追加して、イベント発生のチェックをより安全/簡単にすることができます。(つまり、次のコードは、nullチェックを実行するSafeFire()と呼ばれる拡張メソッドを追加し、(明らかに)イベントを別の変数にコピーして、イベントに影響を与える可能性がある通常のnull競合状態から保護します。

(ただし、nullオブジェクトに拡張メソッドを使用する必要があるかどうかについては、私は2つ考えていますが...)

public static class EventFirer
{
    public static void SafeFire<TEventArgs>(this EventHandler<TEventArgs> theEvent, object obj, TEventArgs theEventArgs)
        where TEventArgs : EventArgs
    {
        if (theEvent != null)
            theEvent(obj, theEventArgs);
    }
}

class MyEventArgs : EventArgs
{
    // Blah, blah, blah...
}

class UseSafeEventFirer
{
    event EventHandler<MyEventArgs> MyEvent;

    void DemoSafeFire()
    {
        MyEvent.SafeFire(this, new MyEventArgs());
    }

    static void Main(string[] args)
    {
        var x = new UseSafeEventFirer();

        Console.WriteLine("Null:");
        x.DemoSafeFire();

        Console.WriteLine();

        x.MyEvent += delegate { Console.WriteLine("Hello, World!"); };
        Console.WriteLine("Not null:");
        x.DemoSafeFire();
    }
}

4
... Action <T>でも同じことはできませんか?SafeFire <T>(このAction <T> theEvent、T theEventArgs)は動作するはずです... "where"を使用する必要はありません
Beachwalker

6

この質問は10年以上前のものであることは承知していますが、最も明白な答えが扱われていないだけでなく、質問からは何が隠されているのかをよく理解しているとは思えないかもしれません。さらに、遅延バインディング、およびデリゲートとラムダに関してそれが何を意味するかについては他にも質問があります(詳細は後ほど)。

まず、部屋で800ポンドのゾウ/ゴリラに対処しますevent。vs Action<T>/ を選択するタイミングはFunc<T>次のとおりです。

  • ラムダを使用して1つのステートメントまたはメソッドを実行します。event実行する複数のステートメント/ラムダ/関数を含むpub / subモデルをさらに必要とする場合に使用します(これは すぐに大きな違いです)。
  • ステートメント/関数を式ツリーにコンパイルする場合は、ラムダを使用します。リフレクションやCOM相互運用で使用されるような、より伝統的な遅延バインディングに参加したい場合は、デリゲート/イベントを使用します。

イベントの例として、次のように小さなコンソールアプリケーションを使用して、シンプルで「標準的な」イベントのセットを結び付けます。

public delegate void FireEvent(int num);

public delegate void FireNiceEvent(object sender, SomeStandardArgs args);

public class SomeStandardArgs : EventArgs
{
    public SomeStandardArgs(string id)
    {
        ID = id;
    }

    public string ID { get; set; }
}

class Program
{
    public static event FireEvent OnFireEvent;

    public static event FireNiceEvent OnFireNiceEvent;


    static void Main(string[] args)
    {
        OnFireEvent += SomeSimpleEvent1;
        OnFireEvent += SomeSimpleEvent2;

        OnFireNiceEvent += SomeStandardEvent1;
        OnFireNiceEvent += SomeStandardEvent2;


        Console.WriteLine("Firing events.....");
        OnFireEvent?.Invoke(3);
        OnFireNiceEvent?.Invoke(null, new SomeStandardArgs("Fred"));

        //Console.WriteLine($"{HeightSensorTypes.Keyence_IL030}:{(int)HeightSensorTypes.Keyence_IL030}");
        Console.ReadLine();
    }

    private static void SomeSimpleEvent1(int num)
    {
        Console.WriteLine($"{nameof(SomeSimpleEvent1)}:{num}");
    }
    private static void SomeSimpleEvent2(int num)
    {
        Console.WriteLine($"{nameof(SomeSimpleEvent2)}:{num}");
    }

    private static void SomeStandardEvent1(object sender, SomeStandardArgs args)
    {

        Console.WriteLine($"{nameof(SomeStandardEvent1)}:{args.ID}");
    }
    private static void SomeStandardEvent2(object sender, SomeStandardArgs args)
    {
        Console.WriteLine($"{nameof(SomeStandardEvent2)}:{args.ID}");
    }
}

出力は次のようになります。

ここに画像の説明を入力してください

Action<int>またはAction<object, SomeStandardArgs>で同じことをした場合、とだけが表示さSomeSimpleEvent2SomeStandardEvent2ます。

では、何が起こっているのeventでしょうか?

展開するFireNiceEventと、コンパイラは実際に次のものを生成します(この説明に関係のないスレッド同期に関する詳細は省略しています)。

   private EventHandler<SomeStandardArgs> _OnFireNiceEvent;

    public void add_OnFireNiceEvent(EventHandler<SomeStandardArgs> handler)
    {
        Delegate.Combine(_OnFireNiceEvent, handler);
    }

    public void remove_OnFireNiceEvent(EventHandler<SomeStandardArgs> handler)
    {
        Delegate.Remove(_OnFireNiceEvent, handler);
    }

    public event EventHandler<SomeStandardArgs> OnFireNiceEvent
    {
        add
        {
            add_OnFireNiceEvent(value)
        }
        remove
        {
            remove_OnFireNiceEvent(value)

        }
    }

コンパイラーは、それが生成されるクラス名前空間からは見えないプライベートデリゲート変数を生成します。そのデリゲートは、サブスクリプション管理と遅延バインディングの参加のために使用されるものであり、公共の対面インタフェースは精通している+=-=オペレータ、我々はすべて知っているようになったと愛:)

FireNiceEventデリゲートのスコープを保護に変更することにより、追加/削除ハンドラーのコードをカスタマイズできます。これにより、開発者はロギングやセキュリティフックなどのカスタムフックをフックに追加できます。これは、ユーザーの役割などに基づいてサブスクリプションへのカスタマイズされたアクセシビリティを可能にするいくつかの非常に強力な機能を実際にもたらします。ラムダでそれを行うことができますか?(実際には、式ツリーをカスタムコンパイルすることでできますが、それはこの応答の範囲外です)

ここでいくつかの応答からいくつかのポイントに対処するには:

  • のargsリストAction<T>を変更することと、から派生したクラスのプロパティを変更することの「脆弱性」に違いはありませんEventArgs。どちらもコンパイルの変更を必要とするだけでなく、両方ともパブリックインターフェイスを変更し、バージョン管理を必要とします。変わりはない。

  • どちらが業界標準であるかに関しては、これが使用されている場所と理由によって異なります。Action<T>IoCやDIでeventよく使用され、GUIやMQタイプのフレームワークなどのメッセージルーティングでよく使用されます。いつもではなく、頻繁に言ったことに注意してください。

  • デリゲートには、ラムダとは異なる有効期間があります。捕獲についても意識する必要があります...閉鎖だけでなく、「猫が引きずり込んだものを見る」という概念も必要です。これは、メモリのフットプリント/寿命だけでなく、リークの管理にも影響します。

もう1つ、以前に参照したもの...遅延バインディングの概念。ラムダがいつ「ライブ」になるかに関して、LINQのようなフレームワークを使用するときに、これがよく見られます。これは、複数回発生する可能性があるデリゲートのレイトバインディングとは大きく異なります(つまり、ラムダは常に存在しますが、バインディングは必要に応じてオンデマンドで発生します)。 -魔法はなくなり、メソッド/プロパティは常にバインドされます。覚えておくべきこと。


4

見つけた標準の.NETイベントパターンを見る

.NETイベントデリゲートの標準シグネチャは次のとおりです。

void OnEventRaised(object sender, EventArgs args);

[...]

引数リストには、送信者とイベント引数の2つの引数が含まれています。送信側のコンパイル時の型はSystem.Objectですが、常に正しい派生型がわかっている場合もあります。慣例により、objectを使用します

同じページの下に、次のような典型的なイベント定義の例があります。

public event EventHandler<EventArgs> EventName;

定義したか

class MyClass
{
  public event Action<MyClass, EventArgs> EventName;
}

ハンドラーは

void OnEventRaised(MyClass sender, EventArgs args);

where senderには正しい(より派生した)タイプがあります。


ハンドラのシグネチャに違いがあることに気付いていないことに申し訳ありません。これは、より正確に型付けされたの 利点になりますsender
user1832484 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.