web-dev-qa-db-ja.com

C#:明示的な追加/削除を伴うイベント!=典型的なイベント?

汎用イベントハンドラーを宣言しました

public delegate void EventHandler();

拡張メソッド 'RaiseEvent'を追加しました:

public static void RaiseEvent(this EventHandler self)        {
   if (self != null) self.Invoke();
}

一般的な構文を使用してイベントを定義すると

public event EventHandler TypicalEvent;

その後、問題なく拡張メソッドを使用して呼び出すことができます。

TypicalEvent.RaiseEvent();

しかし、明示的な追加/削除構文でイベントを定義すると

private EventHandler _explicitEvent;
public event EventHandler ExplicitEvent {
   add { _explicitEvent += value; } 
   remove { _explicitEvent -= value; }
}

その場合、拡張メソッドは、明示的な追加/削除構文で定義されたイベントには存在しません。

ExplicitEvent.RaiseEvent(); //RaiseEvent() does not exist on the event for some reason

イベントにカーソルを合わせると、その理由が表示されます。

イベント「ExplicitEvent」は、+ =または-=の左側にのみ表示できます

通常の構文を使用して定義されたイベントが、明示的な追加/削除構文を使用して定義されたイベントと異なる必要がある理由と、拡張メソッドが後者で機能しない理由

編集:プライベートイベントハンドラーを直接使用して回避できることがわかりました:

_explicitEvent.RaiseEvent();

しかし、典型的な構文を使用して定義されたイベントのように直接イベントを使用できない理由はまだわかりません。多分誰かが私を啓発することができます。

43
user65199

あなたがこれを行うことができるので(それは非現実的なサンプルですが、それは「機能します」):

private EventHandler _explicitEvent_A;
private EventHandler _explicitEvent_B;
private bool flag;
public event EventHandler ExplicitEvent {
   add {
         if ( flag = !flag ) { _explicitEvent_A += value; /* or do anything else */ }
         else { _explicitEvent_B += value; /* or do anything else */ }
   } 
   remove {
         if ( flag = !flag ) { _explicitEvent_A -= value; /* or do anything else */ }
         else { _explicitEvent_B -= value; /* or do anything else */ }
   }
}

コンパイラーは、「ExplicitEvent.RaiseEvent();」で何をすべきかをどのようにして知ることができますか?回答:できません。

「ExplicitEvent.RaiseEvent();」は単なる構文シュガーであり、イベントが暗黙的に実装されている場合にのみ予測できます。

38
TcKs

次のような「フィールドのような」イベントを作成すると、

public event EventHandler Foo;

コンパイラーはフィールドおよびイベントを生成します。イベントを宣言するクラスのソースコード内で、Fooを参照するときはいつでも、コンパイラーはフィールド。ただし、フィールドはプライベートなので、otherクラスからFooを参照するときはいつでも、イベント(したがって、コードの追加/削除)。

独自の明示的な追加/削除コードを宣言した場合、自動生成フィールドは取得されません。したがって、イベントしか取得できず、C#で直接イベントを発生させることはできません。デリゲートインスタンスを呼び出すことしかできません。イベントはデリゲートインスタンスではなく、追加/削除のペアにすぎません。

今、あなたのコードはこれを含んでいました:

public EventHandler TypicalEvent;

これはまだ少し異なります-イベントを宣言していませんでした-それはパブリックフィールドデリゲート型EventHandlerの。 値は単なるデリゲートインスタンスであるため、誰でもで呼び出すことができます。フィールドとイベントの違いを理解することが重要です。 stringintなどの他のタイプのパブリックフィールドは通常はないと確信しているのと同様に、この種のコードは決して記述しないでください。残念ながら、それはタイプミスが簡単で、止めるのが比較的難しいタイプです。コンパイラーが別のクラスからの値を割り当てまたは使用することを許可していたことに気づくことによって、それを見つけるだけです。

詳細については、私の イベントとデリゲートに関する記事 を参照してください。

52
Jon Skeet

それはあなたがそれを正しく見ていなかったからです。ロジックはプロパティと同じです。追加/削除を設定すると、実際のイベントではなく、実際のイベントを公開するラッパーになります(イベントはクラス自体の内部からのみトリガーできるため、常に実際のイベントにローカルでアクセスできます)。

private EventHandler _explicitEvent;
public event EventHandler ExplicitEvent {
   add { _explicitEvent += value; } 
   remove { _explicitEvent -= value; }
}

private double seconds; 
public double Hours
{
    get { return seconds / 3600; }
    set { seconds = value * 3600; }
}

どちらの場合でも、get/setまたはadd/removeプロパティを持つメンバーには、実際にはデータが含まれていません。実際のデータを含めるには、「実際の」プライベートメンバーが必要です。プロパティは、メンバーを外の世界に公開するときに、追加のロジックをプログラムできるようにするだけです。

あなたがそれをしたいと思う理由の良い例は、それが必要ないとき(誰もイベントを聞いていないとき)に余分な計算を停止することです。

たとえば、イベントがタイマーによってトリガーされ、誰もイベントに登録されていない場合にタイマーを機能させたくないとします。

private System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
private EventHandler _explicitEvent;
public event EventHandler ExplicitEvent 
{
   add 
   { 
       if (_explicitEvent == null) timer.Start();
       _explicitEvent += value; 
   } 
   remove 
   { 
      _explicitEvent -= value; 
      if (_explicitEvent == null) timer.Stop();
   }
}

おそらくオブジェクトで追加/削除をロックしたいと思うでしょう(後から考えました)...

8
Yochai Timmer

TypicalEventの "プレーン"宣言は、コンパイラーを巧妙に処理します。イベントメタデータエントリを作成し、メソッドとバッキングフィールドを追加および削除します。コードがTypicalEventを参照する場合、コンパイラーはそれをバッキングフィールドへの参照に変換します。外部コードがTypicalEventを参照する場合(+ =および-=を使用)、コンパイラーはそれをaddまたはremoveメソッドへの参照に変換します。

「明示的」宣言は、このコンパイラーのトリックをバイパスします。 addメソッドとremoveメソッド、およびバッキングフィールドを詳しく説明しています。実際、TcKsが指摘しているように、beバッキングフィールドさえない場合があります(これは明示的な形式を使用する一般的な理由です。例を参照してください。 System.Windows.Forms.Controlのイベント)。したがって、コンパイラはTypicalEventへの参照をバッキングフィールドへの参照に静かに変換できなくなりました。バッキングフィールド、実際のデリゲートオブジェクトが必要な場合は、バッキングフィールドを直接参照する必要があります。

_explicitEvent.RaiseEvent()
1
itowlson