イベントを定義するときに「event」キーワードが必要なのはなぜですか?


108

デリゲートを使用するだけで「event」キーワードを使用せずに同じことができるのに、イベントを定義するときに「event」キーワードが必要な理由がわかりません。

例えば

public delegate void CustomEventHandler(int a, string b);
public event CustomEventHandler customEvent;
customEvent += new CustomEventHandler(customEventHandler);
customEvent(1,"a"); // Raising the event

ここで、2行目から「event」キーワードを削除すると、デリゲートを呼び出してイベントを発生させることもできます。このイベントのキーワードが必要な理由を誰か教えてください。


イベントキーワードを使用しない場合は、クラスオブジェクトを使用してそのイベントにアクセスできる人は、objClass.SelectedIndexChanged = nullのようにNULLに設定してください。これにより、基になるコードがクラッシュします。イベントキーワードにより、ユーザーは+ =を使用してデリゲートに似たものを割り当てることが強制されます。
Sumit Kapadia 2013

回答:


141

フィールドのようなイベントとデリゲート型のパブリックフィールドが見え似ていますが、実際には非常に異なっています。

イベントは基本的にプロパティに似ています。これは、(プロパティの取得/セットではなく)追加/削除メソッドのペアです。フィールドのようなイベント(つまり、追加/削除ビットを自分で指定しないイベント)を宣言すると、パブリックイベントとプライベートバッキングフィールドが作成されます。これにより、イベントをプライベートに発生させることができますが、パブリックサブスクリプションは許可されます。パブリックデリゲートフィールドを使用すると、誰でも他の人のイベントハンドラーを削除したり、自分でイベントを発生させたりすることができます。これはカプセル化の災害です。

イベント(およびデリゲート)の詳細については、このトピックに関する私の記事を参照しください。(ある時点で、これをC#4用に更新する必要があります。これにより、フィールドのようなイベントがわずかに変更されます。ただし、その要旨はまだ正しいです。)


17
これは、MSDNの公式の1行の説明よりも1000倍優れています。「イベントキーワードは、パブリッシャークラスでイベントを宣言するために使用されます。」
カウリネーター

37

eventキーワードは3つの異なることを行います。

  1. インターフェースで通常のフィールドを定義できない場合でも、インターフェースでイベントを定義できます。
  2. =and ()演算子(割り当てと呼び出し)の可視性をプライベートに変更します。これにより、包含クラスのみがイベントを呼び出したり、それに含まれるすべてのメソッドをオーバーライドしたりできます。-=そして+=、オペレータは、まだ(彼らは修飾子は、イベントの隣に書いたアクセスを取得)、それを定義するクラスの外からのイベントで呼び出すことができます。
  3. 方法-=をオーバーライドして、+=イベントで動作することもできます。

2
あなた> MSDN。ありがとう。
M. Azyoksul 2018

26

他の答えは結構です。他に考えるべきことを追加したいと思います。

あなたの質問は、「デリゲート型のフィールドがあるのになぜイベントが必要なのか」ということです。私はその質問を拡張します:デリゲート型のフィールドがある場合、なぜメソッド、プロパティ、イベント、インスタンスコンストラクタ、またはファイナライザが必要なのですか?なぜあなたは必要ですか種類の値と代表者を含むフィールド以外の?なぜ言うだけではないのですか

class C
{
    private int z;
    public readonly Func<int, int> M = (int x)=>{ return x+z; }
    // ... and so on
}

メソッド、プロパティ、イベントは必要ありません。メソッド、プロパティ、イベントのデザインパターンは重要で有用であり、それらを言語で実装するための標準的で文書化された明確な方法を用意するに値するので、私たちはそれを提供します。


4
うわー!C#が大好きな理由を思い出させます!私が使用したすべての言語の中で、コンパクトさ、柔軟性、読みやすいセマンティクスの適切なバランスがあります。比較可能な唯一の言語はObject Pascalです。
ATL_DEV 2013

1
@ATL_DEV:それには理由があります。C#言語のアーキテクトであるAnders Hejlsbergは、以前はObject Pascalに基づく言語であるDelphiのアーキテクトでした。
Eric Lippert

9

eventキーワードを省略するとカプセル化が解除されるため、一部必要です。それが単なるパブリックマルチキャストデリゲートである場合、誰でもそれを呼び出したり、nullに設定したり、改ざんしたりできます。呼び出されたクラスがMailNotifier存在し、それに呼び出されたイベントがある場合MailReceived、他のタイプがを介してそのイベントを起動できるようにしても意味がありませんmailNotifier.MailReceived()

一方、「フィールドのような」イベントに干渉し、それを定義したタイプからのみ呼び出すことができます。

イベント呼び出しを非公開にしたい場合は、次のようなことをやめることは何もありません。

public class MyClassWithNonFieldLikeEvent
{
   private CustomEventHandler m_delegate;

   public void Subscribe(CustomEventHandler handler) 
   {
      m_delegate += handler;        
   }

   public void Unsubscribe(CustomEventHandler handler)
   {          
      m_delegate -= handler;
   }

   private void DoSomethingThatRaisesEvent()
   {
      m_delegate.Invoke(...);
   }       
}

...しかし、これは(多かれ少なかれ)フィールドのようなイベントが既に提供していることを実行するためのコードの全負荷です。


また、デザイナーのようなものを使用するのは難しくなります...「これはイベントです」というパブリックメタデータが存在する代わりに、基本的にメソッドの命名規則に依存します。
Jon Skeet

3

イベントには、デリゲートフィールドと比較して明確な利点があります。イベントは、フィールドとは対照的にインターフェースで定義でき、コードに抽象化を追加できます。さらに重要なことに、イベントは、定義中のクラス内からのみ呼び出すことができます。あなたのケースでは、誰かがイベントを呼び出し、おそらくあなたのコードを破壊する可能性があります。

詳細については、このブログ投稿を参照してください。


2
イベントとデリゲートを実際に比較するべきではありません。イベントとパブリックフィールドをデリゲート型と比較してください。そして、いいえ、フレームワークイベントがその署名を持つことを要求しません。任意のデリゲートタイプのイベントを作成できます。
Jon Skeet

3

デリゲートは参照型です。MulticastDelegateを継承しています。イベントは修飾子です。出来事デリゲートの特別な修飾子です。これは、Invokeメソッドなど、一部の関数/メソッドのアクセシビリティを変更します。修飾子イベントによって変更された後、デリゲートインスタンスは新しい概念「イベント」になります。したがって、イベントは変更されたデリゲートの1つにすぎません。参照を直接変更したり、イベントが定義されたクラスの外部でイベントを呼び出したりすることはできませんが、参照を変更したり、通常のデリゲートインスタンスを呼び出したりすることはできます。イベントは追加の保護を提供するため、イベントにはより多くの安全機能があります。イベントが定義されたクラスの外にいる場合、イベントに対して「+ =」と「-=」の2種類の操作を実行できます。ただし、通常のデリゲートインスタンスのすべてのパブリックフィールド、プロパティ、メソッドなどにアクセスできます。以下はその一例です。

namespace DelegateEvent
{
    //the following line behave as a class. It is indeed a reference type
    public delegate void MyDelegate(string inputs);

    //The following line is illegal. It can only be an instance. so it cannot be directly under namespace
    //public event MyDelegate MyEvent;


    public class MyClassA
    {
        public event MyDelegate MyEventA;
        public MyDelegate MyDelegateA;


        System.Threading.ManualResetEvent MyResetEvent = new System.Threading.ManualResetEvent(false);
        public void TryToDoSomethingOnMyDelegateA()
        {
            if (MyDelegateA != null)
            {
                //User can assecc all the public methods.
                MyDelegateA("I can invoke detegate in classA");         //invoke delegate
                MyDelegateA.Invoke("I can invoke detegate in classA");  //invoke delegate
                IAsyncResult result = MyDelegateA.BeginInvoke("I can invoke detegate in classA", MyAsyncCallback, MyResetEvent);    //Async invoke
                //user can check the public properties and fields of delegate instance
                System.Reflection.MethodInfo delegateAMethodInfo = MyDelegateA.Method;

                MyDelegateA = testMethod;                   //reset reference
                MyDelegateA = new MyDelegate(testMethod);   //reset reference
                MyDelegateA = null;                         //reset reference


                MyDelegateA += testMethod;                  //Add delegate
                MyDelegateA += new MyDelegate(testMethod);  //Add delegate
                MyDelegateA -= testMethod;                  //Remove delegate
                MyDelegateA -= new MyDelegate(testMethod);  //Remove delegate
            }
        }

        public void TryToDoSomethingOnMyEventA()
        {
            if (MyEventA != null)
            {
                MyEventA("I can invoke Event in classA");           //invoke Event
                MyEventA.Invoke("I can invoke Event in classA");    //invoke Event
                IAsyncResult result = MyEventA.BeginInvoke("I can invoke Event in classA", MyAsyncCallback, MyResetEvent);      //Async invoke
                //user can check the public properties and fields of MyEventA
                System.Reflection.MethodInfo delegateAMethodInfo = MyEventA.Method;


                MyEventA = testMethod;                   //reset reference
                MyEventA = new MyDelegate(testMethod);   //reset reference
                MyEventA = null;                         //reset reference


                MyEventA += testMethod;                  //Add delegate
                MyEventA += new MyDelegate(testMethod);  //Add delegate
                MyEventA -= testMethod;                  //Remove delegate
                MyEventA -= new MyDelegate(testMethod);  //Remove delegate
            }
        }

        private void MyAsyncCallback(System.IAsyncResult result)
        {
            //user may do something here
        }
        private void testMethod(string inputs)
        {
            //do something
        }

    }
    public class MyClassB
    {
        public MyClassB()
        {
            classA = new MyClassA();
        }
        public MyClassA classA;
        public string ReturnTheSameString(string inputString)
        {
            return inputString;
        }


        public void TryToDoSomethingOnMyDelegateA()
        {
            if (classA.MyDelegateA != null)
            {
                //The following two lines do the same job --> invoke the delegate instance
                classA.MyDelegateA("I can invoke delegate which defined in class A in ClassB");
                classA.MyDelegateA.Invoke("I can invoke delegate which defined in class A in ClassB");
                //Async invoke is also allowed

                //user can check the public properties and fields of delegate instance
                System.Reflection.MethodInfo delegateAMethodInfo = classA.MyDelegateA.Method;

                classA.MyDelegateA = testMethod;                   //reset reference
                classA.MyDelegateA = new MyDelegate(testMethod);   //reset reference
                classA.MyDelegateA = null;                         //reset reference


                classA.MyDelegateA += testMethod;                  //Add delegate
                classA.MyDelegateA += new MyDelegate(testMethod);  //Add delegate
                classA.MyDelegateA -= testMethod;                  //Remove delegate
                classA.MyDelegateA -= new MyDelegate(testMethod);  //Remove delegate

            }

        }
        public void TryToDoSomeThingMyEventA()
        {
            //check whether classA.MyEventA is null or not is not allowed
            //Invoke classA.MyEventA is not allowed
            //Check properties and fields of classA.MyEventA is not allowed
            //reset classA.MyEventA reference is not allowed

            classA.MyEventA += testMethod;                  //Add delegate
            classA.MyEventA += new MyDelegate(testMethod);  //Add delegate
            classA.MyEventA -= testMethod;                  //Remove delegate
            classA.MyEventA -= new MyDelegate(testMethod);  //Remove delegate
        }

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