事件和代表之间的差异及其各自的应用程序

时间:2009-02-19 01:11:05

标签: c# events delegates

除了语法糖之外,我没有看到使用事件而不是代理的优点。也许我误解了,但似乎事件只是代表的占位符。

你能告诉我差异以及何时使用哪个?有哪些优点和缺点?我们的代码严重依赖于事件,我想深入了解它。

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

10 个答案:

答案 0 :(得分:54)

关键字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使用的非常笨拙的语法中,添加和删除方法更加引人注目(没有运算符重载)。

答案 1 :(得分:48)

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

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

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

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

答案 2 :(得分:12)

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

将活动与界面相结合(更多糖)可以制作令人垂涎欲滴的小吃。代表和纯虚拟抽象类不那么开胃。

答案 3 :(得分:5)

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

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

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

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

答案 4 :(得分:4)

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

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

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

答案 5 :(得分:3)

  

编辑#1 您何时会在事件和对方使用代理?请在生产代码中说明您的真实体验。

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

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

这些代表在运行时通常是非可选的(即不得为null)。

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

答案 6 :(得分:3)

了解你可以看到这两个例子的差异

使用代理(例如,在这种情况下是一种不会返回值的代理)

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. 事件可以包含在接口声明中,而字段不能
  5. 注释

    EventHandler被声明为以下委托:

    public delegate void EventHandler (object sender, EventArgs e)
    

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

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

    的示例

    请参阅here以获取有关EventHandler

    的文档

答案 7 :(得分:2)

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


编辑:我认为我使用模式的不同之处在于,我发现忽略事件是完全可以接受的,它们是钩子/存根,如果你需要了解事件,请听取它们,如果你不这样做关心事件只是忽略它。这就是我将它们用于UI,Javascript /浏览器事件风格的原因。但是,当我有一个委托时,我真的希望有人能够处理委托的任务,如果不处理则抛出异常。

答案 8 :(得分:1)

事件和代表之间的差异比我以前认为的要小很多..我刚刚发布了关于这个主题的超短视频: https://www.youtube.com/watch?v=el-kKK-7SBU

希望这有帮助!

答案 9 :(得分:1)

  

如果我们只使用委托代替Event,那么订阅者有机会克隆(),调用()委托本身,如下图所示。哪个不对。

enter image description here

  

这是b / w事件和委托的主要区别。订户只有一项权利,即收听事件

     

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