1

私はいくつかのクラスを含むクラスライブラリを持っており、各クラスでいくつかのイベントを発生させます。

各イベントには独自のイベント引数のセットがあるため、これらを自動プロパティとしてから継承するクラスに格納しますEventArgs。次に、継承したクラスInvokeの新しいインスタンスを呼び出して渡すだけで、関連するイベントを発生させることができます。EventArgsこれは私が意味することです:

using System;

//My Class Library
namespace MyClassLibrary
{
    //A class
    public class MyClass
    {
        //My event is a field
        public event EventHandler<MyEventHandler> myEvent;

        //I raise my event in this method
        public void InvokeMyEvent()
        {
            //Do some stuff

            //I raise my event here
            myEvent.Invoke(this, new MyEventHandler("The quick brown fox jumps over the lazy dog"));

            //Do some more stuff
        }
    }

    //An event handler, containing some interesting data about the event
    public class MyEventHandler : EventArgs
    {
        //Some interesting data as an automatic property
        public string MyInterestingData { get; private set; }

        //I assign the value of my intersting data in my constructor
        public MyEventHandler(string FooBar)
        {
            MyInterestingData = FooBar;
        }
    }
}

これが私がやりたいことです。これで、私のアプリケーションで、クラスライブラリへの参照を追加し、MyClassそのようにイベントをインスタンス化してサブスクライブできます。これ(汎用イベントハンドラーを使用)の優れている点は、Intellisenseを使用すると、イベントをサブスクライブするときにTabキーを使用できるようになり、メソッドを設定して、デフォルトとしてMyEventHandlerを渡すことができることです。キャストは必要ありません。

using System;
using MyClassLibrary;

namespace ConsoleApplication33
{
    class Program
    {
        static void Main(string[] args)
        {
            MyClass x = new MyClass();
            x.myEvent += new EventHandler<MyEventHandler>(x_myEvent);
        }

        static void x_myEvent(object sender, MyEventHandler e)
        {
            //Doing lots of important stuff

            //Able to access my interesting data in my event handler without casting
            var y = e.MyInterestingData;
        }
    }
}

これはすべて正常にコンパイルされますが、クラスライブラリのポイントは、再利用可能なコードのバンクを確実に持つことです。これは確かにここでの意図です。そこで、クラスライブラリをいくつかのプロジェクトに追加したいと思います。これらのプロジェクトの中には、サブスクライブする必要がmyEventあるものもあれば、サブスクライブしないものもありますが、これらのプロジェクトでクラスの他の機能を使用したいのでmyEvent、将来サブスクライブするオプションがあります。

ただし、サブスクライブしていないプロジェクトでクラスライブラリを使用している場合、がmyEvent発生するたびにランタイムエラーmyEventが発生します。

私は次のように空のメソッドmyEventでのコンストラクターにサブスクライブすることでこれを回避しました:MyClass

public MyClass()
{
    myEvent += new EventHandler<MyEventHandler>(MyClass_myEvent);
}


void MyClass_myEvent(object sender, MyEventHandler e)
{

}

つまり、クラスライブラリを任意のプロジェクトに追加し、インスタンス化MyClassして他の機能を使用し、必要に応じてサブスクライブし、そうでない場合はmyEvent無視することができます。myEvent

これに伴う問題は、MyClassに空のメソッドがあることです。このシナリオを想像してみてください。ただし、約30のイベントがあり、したがって30の空のメソッドがあります。

いくつかの質問

  1. 私は理にかなっていますか?のように、私がこれについてすべて間違っていると思っていても、少なくとも私がやろうとしていることを理解していますか?
  2. ここに実際に問題がありますか、それともこれは私が達成しようとしている機能を達成するためのかなり標準的な方法ですか?

ありがとう

4

7 に答える 7

4

イベントMyEventの一般的な実装は次のとおりです。

protected virtual void OnMyEvent(MyEventArgs eventArgs) {
    var handler = MyEvent;
    if (handler != null) {
        handler(this, eventArgs);
    }
}

次に、イベントをトリガーするときはいつでも、MyEventの代わりにOnMyEventを呼び出します。

于 2012-07-31T14:55:42.087 に答える
3

これが通常、イベントで次のパターンが表示される理由です。

private void OnMyEvent(object sender, MyEventArgs args)
{
    var ev = myEvent;

    if (ev != null)
        ev(sender, args);
}

OnMyEvent(this, new MyEventArgs("The quick brown fox jumps over the lazy dog"));

サブスクライバーがいない場合、イベントはnullになります。このコードはローカルコピーを取り、チェックポイントのイベントが競合状態に関与していないことを確認します。次に、コピーが呼び出されます。これは1つの形式で競合状態を防ぎますが、元のサブスクライバーリストは変更される可能性があり、コピーには表示されないため、完全にスレッドセーフではありません。

正直なところ、私はあなたのやり方でそれを行うことを考えたことも、見たこともありません。私は、空のメソッドサブスクライバーではなく、nullチェックを使用するのが最善だと言います。人々は、後者ではなく前者を期待しています。

また、空のメソッドルートはメモリ/オブジェクトを消費し、ヌルルートはチェックのみを消費します。

余談ですが、MyEventHandlerargumentsクラスは通常。のようなものと呼ばれますMyEventArgs

于 2012-07-31T14:54:01.030 に答える
2

マルチキャストデリゲートは任意の数のサブスクライバーを持つイベントに使用できますが、のパフォーマンスはDelegate.Combine、結果が渡されたデリゲートの1つと等しくなる場合にDelegate.Remove最適化され、のパフォーマンスは、結果がでありnullDelegate.Invoke1つのデリゲートを呼び出す場合に最適化されます(デリゲートOnMyEventがない場合は、nullチェックを実行する方がさらに高速になります)。したがって、空のイベントハンドラーを追加すると、コードの効率が低下します。

まだ言及されていないアプローチは、静的な何もしないデリゲートを作成し、必要に応じて追加/削除ハンドラーを変更することです。サブスクリプションを追加するときに、古いサブスクリプションリストのデリゲートが静的な何もしないデリゲートと一致する場合Interlocked.CompareExchange、新しいデリゲートを保存します。Delegate.Combineそれ以外の場合は、新しいデリゲートを構築するために計算しCompareExchangeます(いずれの場合も、CompareExchange失敗した場合は、add-subscriptionメソッドを再試行します)。サブスクリプションを削除するときは、を使用Delegate.Removeして新しいリストを作成します。nullの場合、静的な何もしないデリゲートに置き換えます。次に、を使用CompareExchangeしてサブスクリプションリストデリゲートを更新します。

Addこのアプローチでは、メソッドとメソッドの速度がわずかに低下しRemoveますが、何もしないデリゲートをリストに残すほどではありません。サブスクライバーがゼロの場合のデリゲート呼び出しは、ヌルチェックよりもわずかに遅くなりますが、サブスクライバーが単一の場合はわずかに速くなります。

于 2012-12-10T16:35:33.057 に答える
1

これを処理する標準的な方法は、サブスクライバーがいないイベントを発生させないことです。

var handler = myEvent;
if (handler != null)
{
    handler(sender, new MyEventArgs());
}

マルチスレッドシナリオで単一のサブスクライバーがチェックと呼び出しのhandler間にサブスクライブを解除した場合に、中間変数に割り当てることで例外が発生するのを回避できます。null

于 2012-07-31T14:55:02.623 に答える
0

通常はnull、イベントを開催する前に確認することです。あなたの場合:

if (myEvent != null)
  myEvent.Invoke(this, new MyEventHandler("The quick brown fox jumps over the lazy dog")); 
于 2012-07-31T14:53:28.267 に答える
0

空のハンドラーでイベントを初期化しても問題はありません。私はいつもそれをします。イベントを何千回も呼び出している場合は、必要な速度よりも遅くなる可能性があります(テスト、測定、決定)...

空のハンドラーを設定するときは、それほど言葉遣いする必要はありません。次のようにすることができます。

myEvent = (sender, args) => { };

メソッドの作成を避けてください...

于 2012-07-31T14:57:03.533 に答える
0

私はあなたが何をしているのか見たいと思います。私は、あなたは空のメソッドのためにあなた自身の仕事を惜しまないことができると思います。次の方法でイベントを呼び出すだけです。

 //I raise my event in this method
    public void InvokeMyEvent()
    {
        //Do some stuff

        //Check if there are subscribers!!
        if (myEvent != null)
          myEvent.Invoke(this, new MyEventHandler("The quick brown fox jumps over the lazy dog"));

        //Do some more stuff
    }
于 2012-07-31T14:57:11.620 に答える