278

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

public void EventName(object sender, EventArgs e);

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


9 답변


566

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

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

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

だからデフォルト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のイベントへの鍵であり、「魔法」を取り除きます。イベントは、実際には、カバーの下にある、同じ「形状」のメソッドのリストにすぎません。リストは、イベントが存在する場所に格納されます。イベントは「発生」し、実際には「このメソッドのリストをたどり、これらの値をパラメータとして使用してそれぞれを呼び出す」だけです。イベントハンドラを割り当てることは、このメソッドのリストに自分のメソッドを追加する簡単で簡単な方法です。と呼ばれる)。


  • そして今、誰でもイベントがEventHandlerと呼ばれる理由を説明できますか?わかりにくい命名規則すべての中で、これは最悪です... - Joel in Gö
  • @Joel in GoイベントはEventHandlerとは呼ばれません - EventHandlerは、イベントと通信するすべての人とイベントが持つ必要がある契約です。 「string MyString」のようなものです。 - 文字列は型を宣言しています。 event MyEventHandler TheEventは、このイベントと対話する人は誰でもMyEventHandler契約に準拠しなければならないことを宣言しています。 Handlerの規約は、契約が主にイベントの処理方法を説明しているためです。 - Rex M
  • イベントはどのように発生しますか? - alchemical
  • @Rex M:" MyEventHandler"の最初の首尾一貫した説明をありがとうございます。私は今まで見たことがある:) - Joel in Gö
  • フェーズをありがとうございます。"イベントと実行されるメソッドの間の接着剤はデリゲートです。 "、これは本当に素晴らしいことです。 - zionpi

92

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

委任

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

MyClass instance = myFactory.GetInstance();

メソッドへの参照を作成するためにデリゲートを使うことができます:

Action myMethod = myFactory.GetInstance;

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

MyClass instance = myMethod();

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

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

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

あなたが持っているのであれば、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つのメソッドのシグネチャを記述するものと考えてください。

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

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

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

イベント

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

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

    Action myField;

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

UIでの使用方法(WinForm)

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

Java

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


  • 説明を応援しますが、イベントは購読者を引き継ぐデリゲートのインスタンスとどう違うのでしょうか。両者はまったく同じもののように見えますか? - BKSpurgeon
  • その理由は@BKSpurgeonあります"購読者を引き継ぐ代表者" - event単なる構文糖であり、それ以上のものではありません。 - Mathieu Guindon
  • " MulticastDelegate"の制限は、(メソッド/デリゲート)シグネチャに戻り値(void)があってはいけないということです。これは正しいとは思わない。戻り値がある場合は最後のものを返します。 - Hozikimaru

38

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

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


  • C#6では、デリゲート呼び出しを次のように単純化できます。OnMaximum?.Invoke(this,new MyEventArgs("you've entered...")); - Rango

32

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

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慣例により基本です。たとえば、次のように定義されているイベントをいくつか見てみましょう。ControlWinFormの場合FrameworkElementWPFでは、追加情報をイベントハンドラに渡すイベントの例を見ることができます。


  • 質問に答えていただき、参加者やイベントに参加していただきありがとうございます。 - divide_byzero
  • 私は使用することに対してお勧めしますOnXXXイベントハンドラの命名パターン(愚かなことに、OnXXXはMFCで'を処理し、.netでは'を上げると解釈されるため、その意味は不明瞭でわかりにくい - 詳細についてはこの記事を参照してください)好ましい名前はRaiseXXXイベントを開催するHandleXXXまたはSender_XXXイベントハンドラのために。 - Jason Williams
  • 簡単なWinFormsアプリケーションを使った実用的な例を示すことができますか? - MC9000

21

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

コンパイラはのデリゲート型を知っているので何かあったイベント、これ:

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


11

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

代表者:

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

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

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

}   

}

リンクされた質問


最近の質問