【问题标题】:Difference between events and delegates and its respective applications [closed]事件和委托及其各自应用程序之间的区别[关闭]
【发布时间】:2010-10-08 11:48:28
【问题描述】:

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

您能向我解释一下这些区别以及何时使用哪个?有什么优点和缺点?我们的代码深深植根于事件,我想深入了解它。

您何时会在事件上使用委托,反之亦然?请在生产代码中说明您对两者的真实体验。

【问题讨论】:

  • 是的,我很难理解这些差异,它们看起来一样,乍一看似乎也一样
  • 另见this question
  • 两个事件和代表之间的区别是事实,而不是意见。该问题要求针对各自的应用程序,因为它们说明了技术解决的问题的差异。这也不是意见问题,因为没有人问哪个是最好的。这个问题的任何部分都不是意见问题,这个陈述也不是意见。在我看来。你拿到徽章了吗?

标签: c# events delegates


【解决方案1】:

关键字event 是多播委托的范围修饰符。这与仅声明多播委托之间的实际区别如下:

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

出于兴趣,您可以将+- 应用于多播委托,这是将委托组合分配给事件的+=-= 语法的基础。这三个 sn-ps 是等价的:

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

add 和 remove 方法在 VB.NET 使用的相当生硬的语法中更加显眼(没有运算符重载)。

【讨论】:

  • + 表示“对多播委托的调用访问仅限于声明类”——对我来说,这是委托和事件之间的关键区别点。
  • 另一个重要的区别(由 itowlson 在下面提到)是不能通过分配给事件来取消订阅所有事件处理程序,但他们可以通过委托来取消订阅。 (顺便说一句,在所有这些中,你的答案对我来说是最有用的)。
  • 尽管 Google 和 stackoverflow 可能很方便,但所有这些以及更多内容都可以在 C# 语言规范中以令人麻木的细节获得,微软免费公开提供。我知道从表面上看,是上帝创造了手册,而 Jon Skeet 吞下了它,但还有其他副本 :)
【解决方案2】:

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

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

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

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

【讨论】:

  • 也许我遗漏了什么,但事件处理程序不是委托的一种吗?
  • 我的回答解决了编辑#1和#2的问题;从使用角度来看的差异。出于本讨论的目的,它们是不同的,即使从技术角度来看,您是正确的。看看技术差异的其他答案。
  • “.Net 中的所有代表都是多播代表”?甚至是返回值的委托?
  • 是的。有关历史,请查看msdn.microsoft.com/en-us/magazine/cc301816.aspx。退房:msdn.microsoft.com/en-us/library/system.delegate.aspx。如果它们返回值,则返回的值是链中最后一个委托的评估。
  • 委托是指向订阅者类中定义的事件处理程序的引用类型。换句话说,委托用作事件(在发布者中)和在订阅者中定义的事件处理程序之间的链接。在一个应用程序中,会有多个订阅者需要监听一个事件,在这种情况下,委托为我们提供了一种链接发布者和订阅者的有效方式。
【解决方案3】:

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

将事件与界面(更多糖)相结合,制作出令人垂涎的小吃。委托和纯虚拟抽象类的吸引力要小得多。

【讨论】:

  • 我也是这么看的。我想要更深入更甜蜜的解释:)
  • 过多的糖会让人发胖,然而... =P
【解决方案4】:

事件在元数据中被标记为这样。这允许 Windows 窗体或 ASP.NET 设计人员将事件与单纯的委托类型属性区分开来,并为它们提供适当的支持(特别是在“属性”窗口的“事件”选项卡上显示它们)。

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

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

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

【讨论】:

    【解决方案5】:

    Edit#1 你什么时候会在事件上使用委托,反之亦然?请在生产代码中说明您对两者的真实体验。

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

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

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

    我倾向于不使用事件;但在我确实使用事件的地方,我将它们用于可选地零个、一个或多个可能感兴趣的客户发送事件信号,即何时一个类(例如System.Windows.Form 类)应该存在并运行,无论是否有任何客户端为其事件添加了事件处理程序(例如表单的“鼠标按下”事件存在,但它是可选的 是否有任何外部客户端有兴趣在该事件上安装事件处理程序)。

    【讨论】:

      【解决方案6】:

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

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

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

      【讨论】:

        【解决方案7】:

        要了解差异,您可以查看这 2 个示例

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

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

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

        你的 Animal 类会以这种方式改变

        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)
        

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

        您也可以使用EventHAndler 代替这个使用EventHandler&lt;ArgsSpecial&gt; 的示例

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

        【讨论】:

          【解决方案8】:

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


          编辑:我认为我在使用模式上的不同之处在于,我发现忽略事件是完全可以接受的,它们是钩子/存根,如果你需要了解事件,请听他们说,如果你不知道'不关心事件只是忽略它。这就是为什么我将它们用于 UI,一种 Javascript/浏览器事件样式。但是,当我有一个委托时,我真的希望有人来处理委托的任务,如果没有处理就会抛出异常。

          【讨论】:

          • 您能否详细说明一下,因为我也在 UI 中使用了偶数?一个很好的例子就足够了......谢谢
          【解决方案9】:

          事件和代表之间的区别比我以前想象的要小得多。我刚刚发布了一个关于这个主题的超短 YouTube 视频: https://www.youtube.com/watch?v=el-kKK-7SBU

          希望这会有所帮助!

          【讨论】:

          • 欢迎来到 Stack Overflow!虽然这在理论上可以回答这个问题,it would be preferable 在此处包含答案的基本部分,并提供参考链接。
          【解决方案10】:

          如果我们只使用委托代替事件,那么订阅者就有机会克隆(),调用()委托本身,如下图所示。这是不对的。

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

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2018-09-08
            • 1970-01-01
            • 2011-05-24
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-09-11
            相关资源
            最近更新 更多