web-dev-qa-db-ja.com

C#のイベントとイベントハンドラを理解する

私はイベントの目的を理解しています、特にユーザーインターフェイスを作成するという文脈の中で。これはイベントを作成するためのプロトタイプだと思います。

public void EventName(object sender, EventArgs e);

イベントハンドラは何をするのか、なぜ必要なのか、そしてどうやって作成するのですか?

295
Levi Campbell

イベントハンドラを理解するためには、 デリゲート を理解する必要があります。 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のイベントへの鍵であり、「魔法」を取り除きます。イベントは実際には、カバーの下にある、同じ「形状」のメソッドのリストにすぎません。リストは、イベントが存在する場所に保管されます。イベントは「発生」し、実際には「このメソッドのリストを調べて、これらの値をパラメータとして使用して各メソッドを呼び出す」だけです。イベントハンドラを割り当てることは、このメソッドのリストに自分のメソッドを追加する方法です。と呼ばれる)。

595
Rex M

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

委任

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

MyClass instance = myFactory.GetInstance();

デリゲートを使ってメソッドへの参照を作成することができます。

Action myMethod = myFactory.GetInstance;

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

MyClass instance = myMethod();

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

1つの中心的な場所からmyFactory.GetInstance()myOfflineFakeFactory.GetInstance()に置き換えることができるようにしたい場合は明らかなことです(別名ファクトリメソッドパターン)。

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

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

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つのメソッドのシグネチャを説明しているとデリゲートは考えます

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

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

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

イベント

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

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

    Action myField;

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

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

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

Java

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

94
tofi9

これは助けるかもしれないコード例です:

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();
    }
  }
}
38
Gary Willoughby

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

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!");
}

FooMyEventを起動するたびに、あなたのOnMyEventハンドラが呼び出されます。

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

34
Andy

ここで既存の優れた答えをさらに追加するために、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はこのパターンのNiceの略記を導入しました。 NULL伝播演算子を使用します。

SomethingHappened?.Invoke("Hi there!");
21
Mathieu Guindon

私の出来事に対する理解は、

代理人:

実行されるメソッドへの参照を保持するための変数。これにより、変数のようなメソッドを渡すことが可能になります。

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

  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();
            }
        }

    }   

}
11
KE50

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

加入者:応答が発生した場所。加入者はイベントに応答する方法を指定する必要があります。これらのメソッドは、デリゲートと同じ種類の引数を取ります。その後、購読者はこのメソッドを発行者の代理に追加します。

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

3
rileyss
//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);
2
Bilgi Sayar

私は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
user3902302

記事の中に素晴らしい技術的な答えがあります。それに付け加えるために、私は技術的に何も持っていません

言語やソフトウェア全般に新しい機能が登場する主な理由の1つは、マーケティングや企業の政治です。 :-)これは過小評価されてはいけません!

これは参加者やイベントへのある程度の拡張にも当てはまると思います!私はそれらが便利だと思い、C#言語に付加価値を付け加えました、しかし一方でJava言語はそれらを使わないことに決めました!彼らは、あなたが代議員と解決しているものは何でもあなたがすでに言語の既存の特徴で解決できることを決定しました。

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

0
Siraf

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

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

重要な点は次のとおりです。

  • イベントは "サブタイプのデリゲート"に似ていますが、(より良い方法で)より制約が強いだけです。実際、イベントの宣言には常にデリゲートが含まれています(EventHandlerはデリゲートの一種です)。

  • イベントハンドラは特定の種類のデリゲート(あなたはそれらをテンプレートと考えることができます)で、特定の「シグネチャ」を持つイベントをユーザに作成させます。署名の形式は、(オブジェクト送信者、EventArgsイベント引数)です。

  • イベントが伝える必要のあるあらゆる種類の情報を含めるために、独自のEventArgsのサブクラスを作成できます。イベントを使用するときにEventHandlerを使用する必要はありません。あなたはそれらを完全に飛ばして、代わりにあなた自身の種類のデリゲートを使うことができます。

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

0
konsfik