事件和委托之间的区别及其各自的应用[关闭]


107

除了语法上的糖以外,我没有看到使用事件优于委托的优势。也许我误会了,但似乎该事件只是代表的占位符。

您能给我解释一下区别和何时使用吗?优点和缺点是什么?我们的代码深深植根于事件,我想深入了解它。

什么时候在事件上使用委托,反之亦然?请在生产代码中说明您在这两个方面的实际经验。


是的,我很难解决差异,它们看起来一样
罗伯特·古尔德,2009年

1
另请参阅此问题
Dimitri C.

1
两个事件与代表之间的区别是事实而不是意见。该问题要求各自的应用程序,因为它们说明了技术所解决的问题的差异。这也不是意见问题,因为没有人问哪个最好。这个问题的任何部分都不是见仁见智的,这个陈述也不是见解。在我看来。你有徽章吗?
彼得·沃尼

Answers:


49

从技术角度来看,其他答案也解决了这些差异。

从语义的角度来看,事件是指满足特定条件时对象引发的动作。例如,我的Stock类具有一个名为Limit的属性,当股票价格达到Limit时会引发一个事件。该通知是通过事件完成的。是否有人真正在乎并订阅该事件,这超出了所有者类的关注范围。

委托是一个更通用的术语,用于描述类似于C / C ++术语中的指针的构造。.Net中的所有委托都是多播委托。从语义的角度来看,它们通常被用作一种输入。特别是,它们是实施策略模式的理想方法。例如,如果要对对象列表进行排序,则可以向该方法提供比较器策略,以告诉实现如何比较两个对象。

我在生产代码中使用了两种方法。当满足某些属性时,我的数据对象会发出通知。最基本的示例,每当属性更改时,都会引发PropertyChanged事件(请参见INotifyPropertyChanged接口)。我在代码中使用委托来提供将某些对象转换为字符串的不同策略。这个特定的示例是针对特定对象类型的美丽的ToString()实现列表,以将其显示给用户。


4
也许我缺少了一些东西,但是事件处理程序不是委托的一种吗?
Powerlord

1
我的回答解决了问题#1和#2;使用方面的差异。出于讨论目的,尽管从技术角度来看您是正确的,但它们是不同的。看看其他解决技术差异的答案。
Szymon Rozga,2009年

3
“ .Net中的所有代理都是多播代理”?甚至代表返回值的代表?
2009年

5
是。有关历史记录,请访问msdn.microsoft.com/en-us/magazine/cc301816.aspx。检出:msdn.microsoft.com/en-us/library/system.delegate.aspx。如果它们返回值,则返回的值是对链中最后一个委托的评估。
Szymon Rozga'3

委托是指向订阅者类中定义的事件处理程序的引用类型。换句话说,委托被用作事件(在发布者中)和订阅者中定义的事件处理程序之间的链接。在一个应用程序中,将需要多个订阅者来监听事件,在这种情况下,代表可以为我们提供一种链接发布者和订阅者的有效方法。
josepainumkal

55

关键字event是多播委托的作用域修饰符。这和仅声明多播委托之间的实际区别如下:

  • 您可以event在界面中使用。
  • 对多播委托的调用访问仅限于声明类。行为就好像委托是私有的。出于分配目的,访问由显式访问修饰符(例如public event)指定。

感兴趣的是,您可以向多播代理应用,+并将其-应用于多播代理,这是将代理组合分配给事件的+=-=语法的基础。这三个片段是等效的:

B = new EventHandler(this.MethodB);
C = new EventHandler(this.MethodC);
A = B + C;

示例二,说明了直接分配和组合分配。

B = new EventHandler(this.MethodB);
C = new EventHandler(this.MethodC);
A = B;
A += C;

示例三:更熟悉的语法。您可能熟悉删除所有处理程序的null分配。

B = new EventHandler(this.MethodB);
C = new EventHandler(this.MethodC);
A = null;
A += B;
A += C;

像属性一样,事件具有一种完全没有人使用的完整语法。这个:

class myExample 
{
  internal EventHandler eh;

  public event EventHandler OnSubmit 
  { 
    add 
    {
      eh = Delegate.Combine(eh, value) as EventHandler;
    }
    remove
    {
      eh = Delegate.Remove(eh, value) as EventHandler;
    }
  }

  ...
}

... 与此完全相同

class myExample 
{
  public event EventHandler OnSubmit;
}

在VB.NET使用的语法非常呆滞的情况下(没有运算符重载),add和remove方法更为明显。


6
+对于“对多播委托的调用访问仅限于声明类”,对我来说,这是委托与事件之间的关键区别。
RichardOD

2
另一个重要的区别(在下面的itowlson中提到)是,不能通过分配给一个事件来取消订阅所有事件处理程序,但是他们可以通过委托来做到这一点。(顺便说一句,在所有这些中,您的回答对我来说是最有用的)。
罗曼·斯塔科夫

4
像Google和stackoverflow一样方便,所有这些以及更多内容都可以在C#语言规范中以麻木的方式获得,Microsoft免费提供。我知道表面上是上帝创造的手册,乔恩·斯凯特(Jon Skeet)吞下了手册,但还有其他副本:)
彼得·沃恩

12

事件是句法糖。它们很美味。当我看到一个事件时,我知道该怎么办。当我看到代表时,我不太确定。

将事件与界面(更多的糖)结合起来,可以制成令人垂涎的小吃。委托和纯虚拟抽象类的吸引力不大。


我也是这样看的。我想要更深入,更甜蜜的解释:)

13
太多的糖会产生一种脂肪,但是... = P
Erik Forbes,2009年

5

事件在元数据中被标记为此类。这使诸如Windows Forms或ASP.NET设计器之类的事件能够将事件与仅代表类型的属性区分开来,并为它们提供适当的支持(特别是在“属性”窗口的“事件”选项卡上显示它们)。

与委托类型的属性的另一个区别是,用户只能添加和删除事件处理程序,而对于委托类型的属性,用户可以设置值:

someObj.SomeCallback = MyCallback;  // okay, replaces any existing callback
someObj.SomeEvent = MyHandler;  // not okay, must use += instead

这有助于隔离事件订阅者:我可以将处理程序添加到事件中,并且可以将处理程序添加到同一事件中,并且不会意外覆盖我的处理程序。


4

尽管事件通常是使用多播委托来实现的,但并不需要以这种方式使用事件。如果一个类公开事件,则意味着该类公开了两个方法。本质上,它们的含义是:

  1. 这是一位代表。当发生有趣的事情时,请调用它。
  2. 这是一位代表。您应该在方便时立即销毁对它的所有引用(不再调用它)。

类处理其公开的事件的最常见方法是定义一个多播委托,并添加/删除传递给上述方法的任何委托,但不要求它们以这种方式工作。不幸的是,事件体系结构无法完成某些使替代方法更加整洁的事情(例如,使订阅方法返回MethodInvoker,该方法将由订阅者保留;取消订阅事件,只需调用返回的方法),因此多播委托是迄今为止最常用的方法。


4

要了解差异,您可以看一下这两个示例

带委托的示例(在这种情况下,动作是一种不返回值的委托)

public class Animal
{
    public Action Run {get; set;}

    public void RaiseEvent()
    {
        if (Run != null)
        {
            Run();
        }
    }
}

要使用委托,您应该执行以下操作

Animale animal= new Animal();
animal.Run += () => Console.WriteLine("I'm running");
animal.Run += () => Console.WriteLine("I'm still running") ;
animal.RaiseEvent();

这段代码很好用,但是您可能会有一些薄弱环节。

例如,如果我写这个

animal.Run += () => Console.WriteLine("I'm running");
animal.Run += () => Console.WriteLine("I'm still running");
animal.Run = () => Console.WriteLine("I'm sleeping") ;

在最后一行代码中,我已经覆盖了先前的行为,只是缺少了一个+(我用+代替+=

另一个弱点是,使用您的Animal班级的每个班级都可以通过RaiseEvent调用它来引发animal.RaiseEvent()

为了避免这种弱点,您可以events在c#中使用。

您的动物类别将以这种方式改变

public class ArgsSpecial :EventArgs
   {
        public ArgsSpecial (string val)
        {
            Operation=val;
        }

        public string Operation {get; set;}
   } 



 public class Animal
    {
       public event EventHandler<ArgsSpecial> Run = delegate{} //empty delegate. In this way you are sure that value is always != null because no one outside of the class can change it

       public void RaiseEvent()
       {  
          Run(this, new ArgsSpecial("Run faster"));
       }
    }

呼叫事件

 Animale animal= new Animal();
 animal.Run += (sender, e) => Console.WriteLine("I'm running. My value is {0}", e.Operation);
 animal.RaiseEvent();

差异:

  1. 您使用的不是公共属性,而是公共字段(发生事件时,编译器会保护您的字段免受不必要的访问)
  2. 无法直接分配事件。在这种情况下,您无法执行我先前显示的覆盖该行为的错误。
  3. 班上没有人可以发起活动。
  4. 事件可以包含在接口声明中,而字段不能

笔记

EventHandler被声明为以下委托:

public delegate void EventHandler (object sender, EventArgs e)

它需要一个(对象类型的)发送者和事件参数。如果发件人来自静态方法,则它为null。

您也可以使用EventHAndler此示例代替EventHandler<ArgsSpecial>

请参阅此处以获取有关EventHandler的文档


3

编辑#1 什么时候在事件和vs.versa上使用委托?请在生产代码中说明这两种情况的实际使用经验。

在设计自己的API时,我定义了作为参数传递给方法或类的构造函数的委托:

  • 这样一个方法可以实现一个简单的“模板方法”模式(例如将PredicateAction委托传递给.Net通用集合类)
  • 或者使类可以执行“回调”(通常是对创建该类的方法的回调)。

这些委托在运行时通常是非可选的(即不能为null)。

我倾向于不使用事件。但是在使用事件的地方,我可以选择使用它们将事件发信号通知给可能感兴趣的零个,一个或多个客户端,即,当有意义的一个类(例如该类)应该存在并运行时,是否有任何客户端具有在其事件中添加了一个事件处理程序(例如,表单的'mouse down'事件存在,但是是否有外部客户端有兴趣在该事件上安装事件处理程序是可选的)。System.Windows.Form


2

尽管没有技术上的原因,但是我在UI样式代码中使用事件,换句话说,在代码的更高级别中使用事件,并使用委托来表示代码中更深层的逻辑。正如我说的那样,您可以使用其中任何一种,但是我发现这种使用模式在逻辑上是合理的,如果没有其他用途,它也有助于记录回调的类型及其层次结构。


编辑:我认为使用模式的不同之处在于,我发现忽略事件是完全可以接受的,它们是钩子/存根,如果您需要了解事件,请听它们,如果您不在乎该事件只是忽略它。这就是为什么我将它们用于UI,一种Javascript / Browser事件样式。但是,当我有一个委托时,我真的希望有人处理委托的任务,如果没有处理,则会引发异常。


当我还使用UI中的evens时,您能否详细说明一下?一个很好的例子就足够了。...谢谢


1

如果我们仅使用委托代替Event,则订阅者将有机会克隆(),调用()委托本身,如下图所示。这是不对的。

在此处输入图片说明

那是黑白事件和委托的主要区别。订户只有一项权利,即听事件

ConsoleLog类通过EventLogHandler订阅日志事件

public class ConsoleLog
{
    public ConsoleLog(Operation operation)
    {
        operation.EventLogHandler += print;
    }

    public void print(string str)
    {
        Console.WriteLine("write on console : " + str);
    }
}

FileLog类通过EventLogHandler订阅日志事件

public class FileLog
{
    public FileLog(Operation operation)
    {
        operation.EventLogHandler += print;
    }

    public void print(string str)
    {
        Console.WriteLine("write in File : " + str);
    }
}

操作类正在发布日志事件

public delegate void logDelegate(string str);
public class Operation
{
    public event logDelegate EventLogHandler;
    public Operation()
    {
        new FileLog(this);
        new ConsoleLog(this);
    }

    public void DoWork()
    {
        EventLogHandler.Invoke("somthing is working");
    }
}
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.