C#のイベントとイベントハンドラーについて


330

イベントの目的、特にユーザーインターフェースを作成する状況について理解しています。これはイベントを作成するためのプロトタイプだと思います:

public void EventName(object sender, EventArgs e);

イベントハンドラーは何をしますか、なぜそれらが必要なのですか、それをどのように作成しますか?


10
@Andyで述べたように、ここのコードスニペットは、イベント自体ではなく、イベントに登録されたメソッドを記述しています。
dthrasher 2009


回答:


661

イベントハンドラーを理解するには、デリゲートを理解する必要があります。ではC#は、メソッドへのポインタ(または参照)としてデリゲートと考えることができます。これは、ポインタを値として渡すことができるため便利です。

デリゲートの中心的な概念は、その署名または形状です。つまり、(1)戻り値の型と(2)入力引数です。我々は、デリゲートを作成する場合たとえば、void MyDelegate(object sender, EventArgs e)それだけでその復帰方法を指し示すことができvoid、かつ取るobjectEventArgs。四角い穴や四角い止め釘のようなものです。したがって、これらのメソッドはデリゲートと同じシグネチャまたは形状を持っていると言います。

メソッドへの参照を作成する方法がわかったので、イベントの目的について考えてみましょう。システムの他の場所で何かが発生したときにコードを実行させたい、つまり「イベントを処理」したいのです。これを行うには、実行するコードの特定のメソッドを作成します。イベントと実行されるメソッドの間の接着剤はデリゲートです。イベントは、イベントが発生したときに呼び出すメソッドへのポインターの「リスト」を内部的に格納する必要があります。*もちろん、メソッドを呼び出すには、それに渡す引数を知る必要があります。イベントと呼び出されるすべての特定のメソッドとの間の「契約」としてデリゲートを使用します。

したがって、デフォルトEventHandler(およびその多く)は、メソッドの特定の形状(これもvoid / object-EventArgs)を表します。イベントを宣言するときは、デリゲートを指定して、そのイベントが呼び出すメソッド(EventHandler)の形状を指定します。

//This delegate can be used to point to methods
//which return void and take a string.
public delegate void MyEventHandler(string foo);

//This event can cause any method which conforms
//to MyEventHandler to be called.
public event MyEventHandler SomethingHappened;

//Here is some code I want to be executed
//when SomethingHappened fires.
void HandleSomethingHappened(string foo)
{
    //Do some stuff
}

//I am creating a delegate (pointer) to HandleSomethingHappened
//and adding it to SomethingHappened's list of "Event Handlers".
myObj.SomethingHappened += new MyEventHandler(HandleSomethingHappened);

//To raise the event within a method.
SomethingHappened("bar");

(*これは.NETのイベントの鍵であり、「マジック」をはぎ取る-イベントは、実際には、実際には同じ「形状」のメソッドのリストです。リストは、イベントが存在する場所に保存されます。イベントは「発生」します。これは、実際には「このメソッドのリストを調べて、これらの値をパラメーターとして使用して各メソッドを呼び出す」だけです。イベントハンドラーの割り当ては、このメソッドのリストにメソッドを追加するより簡単な方法です呼び出されます)。


24
そして今、誰もがイベントがEventHandlerと呼ばれている理由を説明できますか?紛らわしいすべての命名規則の中で、これは最悪です...
Joel inGöJun

37
@Joel in GoイベントはEventHandlerと呼ばれません-EventHandlerは、イベントと通信するすべての人とのイベントの契約です。それは「string MyString」のようなものです-文字列は型を宣言しています。イベントMyEventHandler TheEventは、このイベントを操作するすべての人がMyEventHandlerコントラクトに準拠する必要があることを宣言しています。ハンドラー規約は、コントラクトが主にイベントの処理方法を記述しているためです。
Rex M

18
イベントはどのように発生しますか?
錬金術

17
@Rex M:私が今まで見た "MyEventHandler"の最初の首尾一貫した説明をありがとう:)
Joel

10
フェーズをありがとう:「イベントと実行されるメソッドの間の接着剤はデリゲートです。」これは本当に素晴らしいです。
zionpi 2013年

103

C#は、二つの用語を知っている、delegateevent。最初のものから始めましょう。

委任

A delegateはメソッドへの参照です。インスタンスへの参照を作成できるのと同じように:

MyClass instance = myFactory.GetInstance();

デリゲートを使用して、メソッドへの参照を作成できます。

Action myMethod = myFactory.GetInstance;

これでメソッドへの参照ができたので、参照を介してメソッドを呼び出すことができます。

MyClass instance = myMethod();

しかし、なぜあなたは?myFactory.GetInstance()直接電話することもできます。この場合はできます。ただし、アプリケーションの残りの部分に知識を持たせmyFactoryたり、myFactory.GetInstance()直接呼び出したりしたくない場合については、多くの場合を考えます。

あなたは置き換えることができるようにしたい場合は明白なものであるmyFactory.GetInstance()myOfflineFakeFactory.GetInstance()一つの中央の場所(別名からFactory Methodパターン)。

ファクトリーメソッドパターン

したがって、TheOtherClassクラスがあり、それを使用する必要がある場合、myFactory.GetInstance()デリゲートなしのコードは次のようになります(TheOtherClassのタイプを通知する必要がありますmyFactory)。

TheOtherClass toc;
//...
toc.SetFactory(myFactory);


class TheOtherClass
{
   public void SetFactory(MyFactory factory)
   {
      // set here
   }

}

デリゲートを使用する場合は、私のファクトリーのタイプを公開する必要はありません。

TheOtherClass toc;
//...
Action factoryMethod = myFactory.GetInstance;
toc.SetFactoryMethod(factoryMethod);


class TheOtherClass
{
   public void SetFactoryMethod(Action factoryMethod)
   {
      // set here
   }

}

したがって、型を公開せずに、使用する他のクラスにデリゲートを与えることができます。公開しているのは、メソッドのシグネチャ(パラメーターの数など)だけです。

「私の方法の署名」、どこでそれを以前に聞いたのですか?はい、インターフェース!!! インターフェイスは、クラス全体の署名を記述します。デリゲートは、1つのメソッドのシグネチャのみを説明していると考えてください。

インターフェースとデリゲートのもう1つの大きな違いは、クラスを作成するときに、C#に「このメソッドはそのタイプのデリゲートを実装する」と言う必要がないことです。インターフェースの場合、「このクラスはそのタイプのインターフェースを実装する」と言う必要があります。

さらに、デリゲート参照は(いくつかの制限付きで、以下を参照)複数のメソッド(と呼ばれるMulticastDelegate)を参照できます。つまり、デリゲートを呼び出すと、明示的に接続された複数のメソッドが実行されます。オブジェクト参照は常に1つのオブジェクトのみを参照できます。

以下のための制限は、MulticastDelegate(メソッド/デリゲート)署名が任意の戻り値を(持っているべきではないとしているvoid)と、キーワードoutref署名に使用されていません。明らかに、数値を返す2つのメソッドを呼び出して、それらが同じ数値を返すことを期待することはできません。署名が準拠すると、デリゲートは自動的にになりMulticastDelegateます。

イベント

イベントは、他のオブジェクトからのデリゲートへのサブスクリプションを公開する単なるプロパティ(get; set;インスタンスフィールドのプロパティなど)です。ただし、これらのプロパティはget; set;をサポートしていません。代わりに、追加をサポートしています。削除する;

だからあなたは持つことができます:

    Action myField;

    public event Action MyProperty
    {
        add { myField += value; }
        remove { myField -= value; }
    }

UIでの使用(WinForms、WPF、UWPなど)

これで、デリゲートがメソッドへの参照であり、デリゲートから参照されるメソッドを提供できることを世界中に知らせるイベントを作成できることがわかりました。それから、UIボタンです。私がクリックされたかどうかに興味がある人なら誰でも、その方法を(登録したイベントを介して)登録するように依頼できます。与えられたメソッドをすべて使用して、デリゲートから参照できます。次に、ユーザーが来てそのボタンをクリックするまで待機します。それから、デリゲートを呼び出す十分な理由があります。また、デリゲートは指定されたすべてのメソッドを参照するため、これらのすべてのメソッドが呼び出されます。これらのメソッドが何を実行するのか、またどのクラスがそれらのメソッドを実装するのかもわかりません。私たちが気にしているのは、誰かがクリックされることに興味を持っていたことです。

ジャワ

Javaのような言語にはデリゲートはありません。代わりにインターフェースを使用します。彼らが行う方法は、「私たちがクリックされる」ことに関心のある人に、特定のインターフェースを(私たちが呼び出すことができる特定のメソッドで)実装するように依頼し、そのインターフェースを実装するインスタンス全体を提供することです。このインターフェイスを実装するすべてのオブジェクトのリストを保持しており、クリックされるたびに「呼び出すことができる特定のメソッド」を呼び出すことができます。


説明を応援しますが、イベントはサブスクライバーを引き継ぐデリゲートのインスタンスとどのように異なりますか?どちらもまったく同じように見えますか?
BKSpurgeon 2016年

@BKSpurgeon は、「サブスクライバーを引き受けるデリゲート」であるためeventです。これは単なる構文糖であり、それ以上のものではありません。
Mathieu Guindon 2017年

「MulticastDelegateの制限は、(メソッド/デリゲート)シグネチャに戻り値(void)があってはならないことです」、これは正しいとは思いません。戻り値がある場合は、最後の値を返します。
Hozikimaru 2017年

「したがって、型を公開せずに、使用する他のクラスにデリゲートを与えることができます。公開しているのは、メソッドのシグネチャだけです...」 -私にとってそれは重要なポイントです。ありがとうございました!
ライアン

40

これは、実際にはイベントハンドラの宣言です。イベントが発生すると呼び出されるメソッドです。イベントを作成するには、次のように記述します。

public class Foo
{
    public event EventHandler MyEvent;
}

そして、あなたはこのようにイベントにサブスクライブすることができます:

Foo foo = new Foo();
foo.MyEvent += new EventHandler(this.OnMyEvent);

OnMyEvent()が次のように定義されている場合:

private void OnMyEvent(object sender, EventArgs e)
{
    MessageBox.Show("MyEvent fired!");
}

たびFooオフ発射しMyEvent、その後、あなたのOnMyEventハンドラが呼び出されます。

のインスタンスをEventArgs2番目のパラメーターとして常に使用する必要はありません。追加情報を含めたい場合は、EventArgsEventArgsは基本的に)から派生したクラスを使用できます。たとえばControl、WinFormsまたはFrameworkElementWPFで定義されているいくつかのイベントを見ると、イベントハンドラーに追加情報を渡すイベントの例を見ることができます。


14
質問に回答していただき、代議員やイベントに参加しないでいただきありがとうございます。
divide_byzero

3
OnXXXイベントハンドラーの命名パターンを使用しないことをお勧めします。(愚かにも、OnXXXはMFCで「ハンドルXXX」を意味し、.netで「raise XXX」を意味するように解釈されているため、その意味は不明確で紛らわしい- 詳細はこの投稿を参照)。優先される名前はRaiseXXX、イベントを発生させること、HandleXXXまたはSender_XXXイベントハンドラーの名前です。
Jason Williams

1
簡単なWinFormsアプリケーションで実際に動作する例を示すことができますか?
MC9000 2018年

40

これは役立つかもしれないコード例です:

using System;
using System.Collections.Generic;
using System.Text;

namespace Event_Example
{
  // First we have to define a delegate that acts as a signature for the
  // function that is ultimately called when the event is triggered.
  // You will notice that the second parameter is of MyEventArgs type.
  // This object will contain information about the triggered event.

  public delegate void MyEventHandler(object source, MyEventArgs e);

  // This is a class which describes the event to the class that receives it.
  // An EventArgs class must always derive from System.EventArgs.

  public class MyEventArgs : EventArgs
  {
    private string EventInfo;

    public MyEventArgs(string Text) {
      EventInfo = Text;
    }

    public string GetInfo() {
      return EventInfo;
    }
  }

  // This next class is the one which contains an event and triggers it
  // once an action is performed. For example, lets trigger this event
  // once a variable is incremented over a particular value. Notice the
  // event uses the MyEventHandler delegate to create a signature
  // for the called function.

  public class MyClass
  {
    public event MyEventHandler OnMaximum;

    private int i;
    private int Maximum = 10;

    public int MyValue
    {
      get { return i; }
      set
      {
        if(value <= Maximum) {
          i = value;
        }
        else 
        {
          // To make sure we only trigger the event if a handler is present
          // we check the event to make sure it's not null.
          if(OnMaximum != null) {
            OnMaximum(this, new MyEventArgs("You've entered " +
              value.ToString() +
              ", but the maximum is " +
              Maximum.ToString()));
          }
        }
      }
    }
  }

  class Program
  {
    // This is the actual method that will be assigned to the event handler
    // within the above class. This is where we perform an action once the
    // event has been triggered.

    static void MaximumReached(object source, MyEventArgs e) {
      Console.WriteLine(e.GetInfo());
    }

    static void Main(string[] args) {
      // Now lets test the event contained in the above class.
      MyClass MyObject = new MyClass();
      MyObject.OnMaximum += new MyEventHandler(MaximumReached);
      for(int x = 0; x <= 15; x++) {
        MyObject.MyValue = x;
      }
      Console.ReadLine();
    }
  }
}

4
:C#6に簡略化することで、デリゲート呼び出しCANOnMaximum?.Invoke(this,new MyEventArgs("you've entered..."));
ティムSchmelter

23

ここで既存の素晴らしい答えに追加するだけです-受け入れられたもののコードに基づいて構築しdelegate void MyEventHandler(string foo)ます...

コンパイラは、SomethingHappenedイベントのデリゲート型を知っているため、次のようになります。

myObj.SomethingHappened += HandleSomethingHappened;

完全に以下と同等です:

myObj.SomethingHappened += new MyEventHandler(HandleSomethingHappened);

また、ハンドラーは次のように登録解除することもできます-=

// -= removes the handler from the event's list of "listeners":
myObj.SomethingHappened -= HandleSomethingHappened;

完全を期すために、イベントの発生は、イベントを所有するクラスでのみ、次のように行うことができます。

//Firing the event is done by simply providing the arguments to the event:
var handler = SomethingHappened; // thread-local copy of the event
if (handler != null) // the event is null if there are no listeners!
{
    handler("Hi there!");
}

ハンドラのスレッドローカルコピーは必ず呼び出しがスレッドセーフであることを確認するために必要とされる-それ以外のスレッドが行くとイベントの最後のハンドラの登録を解除し、我々がチェックした直後にそれがあった場合は可能性がありnull、私たちは「楽しい」を持っているでしょうNullReferenceExceptionが。


C#6では、このパターンに適した短い表記が導入されました。null伝播演算子を使用します。

SomethingHappened?.Invoke("Hi there!");

13

イベントについての私の理解は、

代理人:

実行するメソッドへの参照を保持する変数。これにより、変数などのメソッドを渡すことができます。

イベントを作成して呼び出す手順:

  1. イベントはデリゲートのインスタンスです

  2. イベントはデリゲートのインスタンスなので、最初にデリゲートを定義する必要があります。

  3. イベントが発生したときに実行される1つまたは複数のメソッドを割り当てる(デリゲートの呼び出し

  4. イベントを起動する(デリゲートを呼び出す

例:

using System;

namespace test{
    class MyTestApp{
        //The Event Handler declaration
        public delegate void EventHandler();

        //The Event declaration
        public event EventHandler MyHandler;

        //The method to call
        public void Hello(){
            Console.WriteLine("Hello World of events!");
        }

        public static void Main(){
            MyTestApp TestApp = new MyTestApp();

            //Assign the method to be called when the event is fired
            TestApp.MyHandler = new EventHandler(TestApp.Hello);

            //Firing the event
            if (TestApp.MyHandler != null){
                TestApp.MyHandler();
            }
        }

    }   

}

3

パブリッシャー:イベントが発生する場所。パブリッシャーは、クラスが使用しているデリゲートを指定して必要な引数を生成し、それらの引数とそれ自体をデリゲートに渡す必要があります。

サブスクライバー:応答が発生する場所。サブスクライバーは、イベントに応答するメソッドを指定する必要があります。これらのメソッドは、デリゲートと同じタイプの引数を取る必要があります。サブスクライバーは、このメソッドをパブリッシャーのデリゲートに追加します。

したがって、パブリッシャーでイベントが発生すると、デリゲートはいくつかのイベント引数(データなど)を受け取りますが、パブリッシャーはこれらのすべてのデータで何が起こるかわかりません。サブスクライバーは、パブリッシャーのクラスのイベントに応答する独自のクラスにメソッドを作成できるため、サブスクライバーはパブリッシャーのイベントに応答できます。


2
//This delegate can be used to point to methods
//which return void and take a string.
public delegate void MyDelegate(string foo);

//This event can cause any method which conforms
//to MyEventHandler to be called.
public event MyDelegate MyEvent;

//Here is some code I want to be executed
//when SomethingHappened fires.
void MyEventHandler(string foo)
{
    //Do some stuff
}

//I am creating a delegate (pointer) to HandleSomethingHappened
//and adding it to SomethingHappened's list of "Event Handlers".
myObj.MyEvent += new MyDelegate (MyEventHandler);

0

私はKE50に同意します。ただし、イベントには実行するアクションのコレクション(デリゲート)が含まれているため、「event」キーワードを「ActionCollection」のエイリアスと見なしています。

using System;

namespace test{

class MyTestApp{
    //The Event Handler declaration
    public delegate void EventAction();

    //The Event Action Collection 
    //Equivalent to 
    //  public List<EventAction> EventActions=new List<EventAction>();
    //        
    public event EventAction EventActions;

    //An Action
    public void Hello(){
        Console.WriteLine("Hello World of events!");
    }
    //Another Action
    public void Goodbye(){
        Console.WriteLine("Goodbye Cruel World of events!");
    }

    public static void Main(){
        MyTestApp TestApp = new MyTestApp();

        //Add actions to the collection
        TestApp.EventActions += TestApp.Hello;
        TestApp.EventActions += TestApp.Goodbye;

        //Invoke all event actions
        if (TestApp.EventActions!= null){
            //this peculiar syntax hides the invoke 
            TestApp.EventActions();
            //using the 'ActionCollection' idea:
            // foreach(EventAction action in TestApp.EventActions)
            //     action.Invoke();
        }
    }

}   

}

0

投稿で素晴らしい技術的回答!それに技術的に追加するものは何もありません。

言語やソフトウェア全般に新機能が登場する主な理由の1つは、マーケティングや会社の政治です。:-)これは概算を下回ってはなりません!

これは、デリゲートとイベントの特定の範囲にも当てはまると思います。私はそれらが有用であるとわかり、C#言語に価値を加えていますが、一方でJava言語はそれらを使用しないことにしました!彼らはあなたがデリゲートで解決しているものは何でも、言語の既存の機能、すなわちインターフェースですでに解決できると決めました

2001年頃、Microsoftは.NETフレームワークとC#言語をJavaの競合ソリューションとしてリリースしました。そのため、Javaにはない新しい機能があるのは良かったです。


0

最近、c#でイベントを使用する方法の例を作成し、ブログに投稿しました。私は非常に単純な例で、それをできるだけ明確にしようとしました。誰かを助けるかもしれない場合は、ここにあります:http : //www.konsfik.com/using-events-in-csharp/

説明とソースコード(多くのコメントを含む)が含まれ、主にイベントとイベントハンドラーの適切な(テンプレートのような)使用法に焦点を当てています。

重要なポイントは次のとおりです。

  • イベントは「サブタイプのデリゲート」のようなものであり、(良い意味で)より制約されています。実際、イベントの宣言には常にデリゲートが含まれます(EventHandlerはデリゲートの一種です)。

  • イベントハンドラーは、特定の種類のデリゲート(テンプレートと考えることもできます)であり、特定の "署名"を持つイベントをユーザーに作成させる必要があります。署名の形式は次のとおりです(オブジェクト送信者、EventArgsイベント引数)。

  • イベントが伝える必要のあるあらゆるタイプの情報を含めるために、EventArgsの独自のサブクラスを作成できます。イベントを使用する場合は、EventHandlerを使用する必要はありません。それらを完全にスキップして、代わりに独自のデリゲートを使用できます。

  • イベントとデリゲートの使用の主な違いの1つは、イベントはパブリックとして宣言されていても、宣言されたクラス内からしか呼び出せないことです。これは非常に重要な違いです。イベントを公開して外部メソッドに「接続」すると同時に、「外部の誤用」から保護するためです。


0

もう1つ知っておくべきこと、場合によっては、低レベルの結合が必要なときにデリゲート/イベントを使用する必要があります。

アプリケーションの複数の場所でコンポーネント使用する場合は、低レベルのカップリングでコンポーネントを作成する必要があり、特定の関係のないロジックをコンポーネントの外部で委任する必要があります。これにより、分離されたシステムとクリーンなコードが確実に得られます。

SOLID原理これは" D "、(Dの ependency反転原理)。

IoC」とも呼ばれ、制御の反転

イベント、デリゲート、DI(依存性注入)で「IoC」を作成できます。

子クラスのメソッドにアクセスするのは簡単です。しかし、子から親クラスのメソッドにアクセスするのはより困難です。親参照を子に渡す必要があります!(またはインターフェイスでDIを使用)

デリゲート/イベントを使用すると、参照なしで子から親に通信できます。

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

上記のこの図では、Delegate / Event使用していません。Aのメソッドで関係のないビジネスロジックを実行するには、親コンポーネントB 親コンポーネントAの参照を持っている必要があります(高レベルの結合)。

このアプローチでは、コンポーネントBを使用するすべてのコンポーネントのすべての参照を配置する必要があります。:(

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

上記のこの図では、Delegate / Eventを使用しており、コンポーネントBはAを知っている必要はありません(低レベルの結合)。

また、コンポーネントBをアプリケーションのどこにでも使用できます

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