为什么在定义事件时需要“ event”关键字?


109

我不明白为什么在定义事件时为什么需要“事件”关键字,而仅使用委托就可以在不使用“事件”关键字的情况下完成相同的事情。

例如

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

在这里,如果我从第二行中删除“ event”关键字,那么我也可以通过调用委托引发事件。有人可以告诉我为什么需要该事件关键字吗?


如果您不使用事件关键字,那么可以使用类对象访问该事件的任何人都可以将其设置为NULL,例如objClass.SelectedIndexChanged = null。这会使您的基础代码崩溃。event关键字强制用户使用+ =分配类似于委托的内容。
Sumit Kapadia

Answers:


142

类似字段的事件和代表类型的公共字段看起来相似,但实际上却大不相同。

事件从根本上来说就像一个属性-它是一对添加/删除方法(而不是属性的获取/设置)。当您声明类似字段的事件(即您自己不指定添加/删除位的事件)时,将创建一个公共事件和一个私有后备字段。这使您可以私下引发事件,但可以公开订阅。使用公共委托字段,任何人都可以删除其他人的事件处理程序,自行引发事件,等等-这是封装灾难。

有关事件(和代表)的更多信息,请阅读有关该主题的文章。(在某些时候,我需要针对C#4对此进行更新,它会稍微更改类似于字段的事件。尽管如此,其要点仍然是正确的。)


17
这比MSDN的官方单行解释好一千倍:“ event关键字用于在发布者类中声明事件”。
cowlinator

37

event关键字执行3项不同的操作:

  1. 即使不能在接口中定义常规字段,也可以在接口中定义事件。
  2. 它将=()运算符(分配和调用)的可见性更改为私有,以便只有包含类可以调用事件或覆盖其中包含的所有方法。在-=+=运营商仍然可以在一个事件的类定义它(他们得到的访问修饰符你旁边的事件写)援引来自外部。
  3. 您还可以覆盖事件的行为方式-=+=行为。

2
您> MSDN。谢谢。
M. Azyoksul '18年

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

1
@ATL_DEV:这是有原因的。C#语言的设计师Anders Hejlsberg以前是Delphi的设计师,Delphi是一种基于Object Pascal的语言。
埃里克·利珀特

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

...但这只是(或多或少)执行类似字段的事件已经给我们提供的全部代码。


对于像设计者这样的事情,使用起来也将更加困难。。。基本上,您将依赖方法的命名约定,而不是使用公开的元数据说“这是一个事件”。
乔恩·斯基特

3

与委托字段相比,事件具有明显的优势。可以在与字段相反的接口中定义事件,从而为代码添加抽象,甚至更重要的是:只能从定义类内部调用事件。就您而言,任何人都可以调用该事件,有可能破坏您的代码。

有关更多信息,请参见此博客文章


2
您实际上不应该比较事件和委托 - 使用委托类型比较事件和公共字段。不,框架要求事件有签名。您可以创建任何喜欢的委托类型的事件。
乔恩·斯基特

3

委托是引用类型。它继承了MulticastDelegate事件是一个修饰符。事件是代表的特殊修饰符。它修改了某些功能/方法的可访问性,例如调用方法。通过修饰符事件修改后,委托实例成为新概念“事件”。因此,Event只是一个修改的委托。您不能在定义事件的类之外直接更改引用或调用事件,但是可以更改引用或调用普通的委托实例。事件提供了额外的保护,因此事件具有更多的安全功能。当您在定义事件的类之外时,可以对事件执行两种操作:“ + =”和“-=”。但是您可以访问普通委托实例的所有公共字段,属性,方法等。这是一个例子:

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
        }
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.