代表和事件之间有什么区别?


Answers:


280

一个事件的声明增加了抽象和保护上一层委托实例。此保护可防止委托的客户端重置委托及其调用列表,并且仅允许在调用列表中添加或删除目标。


43
如果可以的话,此保护层还可以防止“客户端”(定义类/结构外部的代码)调用委托,以及以任何方式获取事件“背后”的委托对象。
杰普·斯蒂格·尼尔森

7
并非完全正确。您可以声明一个没有后端委托实例的事件。在c#中,您可以显式实现事件,并使用您选择的其他后端数据结构。
Miguel Gamboa

3
@mmcdole您能提供一个例子来解释他吗?
vivek nuna

102

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

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

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

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

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

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

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

呼叫事件

 Animal 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<ArgsSpecial>,也可以使用编写EventHandler

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


7
一切都很好,直到我遇到“班上没有人可以发起活动”。这意味着什么?RaiseEvent只要调用方法可以访问animal使用事件的代码中的实例,任何人都无法调用吗?
dance2die 2014年

11
@Sung Events只能从班级内部提出,也许我不清楚对此进行解释。使用事件,您可以调用引发事件(封装)的函数,但是只能从定义事件的类内部进行调用。让我知道是否不清楚。
faby

1
“不能直接分配事件。” 除非我理解您错了,否则事实并非如此。这是一个示例:gist.github.com/Chiel92/36bb3a2d2ac7dd511b96
Chiel 10 Brinke

2
@faby,您的意思是,即使该事件被宣布为公共事件,我仍然无法执行animal.Run(this, new ArgsSpecial("Run faster");
2013年

1
@ChieltenBrinke当然,可以在类的成员中分配事件但不能以其他方式分配。
Jim Balter

93

除了语法和操作属性外,还存在语义上的差异。

从概念上讲,代表是功能模板。也就是说,他们表示必须履行职能的合同,才能被视为代表的“类型”。

事件代表……嗯,事件。它们旨在在某事发生时向某人发出警报,是的,他们遵循委托定义,但并非同一个人。

即使它们是完全相同的东西(在语法上和IL代码中),仍将保留语义上的差异。通常,我喜欢为两个不同的概念使用两个不同的名称,即使它们以相同的方式实现(这并不意味着我希望两次拥有相同的代码)。


8
代表们的精彩描述。
桑普森2009年

1
那么我们可以说事件是委托的“特殊”类型吗?
巴氏

我不明白你的意思,你可以使用一个代表来``在发生事情时提醒某人''。也许您不会这样做,但是您可以并且因此不是事件的固有属性。
史蒂夫

@JorgeCórdoba代表和活动的示例代表是报纸的所有者和事件(订阅或取消订阅),有些人购买报纸,有些人不购买报纸,这意味着报纸所有者不能强迫每个人购买报纸。对还是错?
Rahul_Patil

37

这是另一个值得参考的链接。 http://csharpindepth.com/Articles/Chapter2/Events.aspx

简要地讲,本文的主要内容-事件是对委托的封装。

引用文章:

假设事件在C#/。NET中不存在。另一个班级将如何订阅事件?三种选择:

  1. 公共委托变量

  2. 由属性支持的委托变量

  3. 具有AddXXXHandler和RemoveXXXHandler方法的委托变量

选项1显然是可怕的,出于所有正常原因,我们都讨厌公共变量。

选项2稍好一些,但允许订阅者有效地相互覆盖-编写someInstance.MyEvent = eventHandler太容易了。这将替换任何现有的事件处理程序,而不是添加一个新的事件处理程序。此外,您仍然需要编写属性。

选项3基本上是事件为您提供的内容,但是如果您对类似字段的事件给您带来的语义感到满意,则可以使用保证的约定(由编译器生成并由IL中的额外标志提供支持)和“免费”实现。订阅和取消订阅事件是封装在一起的,不允许任意访问事件处理程序列表,并且语言可以通过提供声明和订阅的语法来简化事情。


简洁明了的解释。Thanx
Pap

从理论上讲,这比什么都重要,但是FWIW我总是觉得“选项1不好,因为我们不喜欢公共变量”这一论点可能需要更多说明。如果他说这是因为它是“不良的OOP习惯”,那么从技术上讲,一个public Delegate变量将暴露“数据”,但是据我所知,OOP从未提及过任何类似于“ Delegate(它既不是“对象”也不是消息”)的概念。 ,而.NET确实几乎不将委托视为数据。
jrh

尽管我也想提供更多实用的建议,但是如果您想确保只有一个处理程序,那么AddXXXHandler使用private Delegate变量创建自己的方法可能是个不错的选择。在这种情况下,您可以检查是否已设置处理程序,并做出适当的反应。如果您需要持有的对象Delegate能够清除所有处理程序,那么这也可能是一个不错的设置(event没有任何方法可以执行此操作)。
jrh

7

注意:如果您有权访问C#5.0 Unleashed,请阅读第18章标题为“事件”的“普通使用委托的限制”,以更好地理解两者之间的区别。


举一个简单而具体的例子总是有帮助的。所以这里是社区的。首先,我展示了如何单独使用委托来完成事件为我们所做的事情。然后,我将说明相同的解决方案如何与的实例一起使用EventHandler。然后,我解释了为什么我们不想做我在第一个示例中解释的事情。这篇文章的灵感来自John Skeet 的一篇文章

示例1:使用公共委托

假设我有一个带有单个下拉框的WinForms应用程序。下拉列表绑定到List<Person>。其中Person具有ID,Name,NickName,HairColor的属性。在主窗体上是显示该人的属性的自定义用户控件。当某人在下拉列表中选择一个人时,用户控件中的标签将更新以显示所选人的属性。

在此处输入图片说明

这是这样的。我们有三个文件可帮助我们将它们组合在一起:

  • Mediator.cs-静态类保存委托
  • Form1.cs-主要形式
  • DetailView.cs-用户控件显示所有详细信息

这是每个类的相关代码:

class Mediator
{
    public delegate void PersonChangedDelegate(Person p); //delegate type definition
    public static PersonChangedDelegate PersonChangedDel; //delegate instance. Detail view will "subscribe" to this.
    public static void OnPersonChanged(Person p) //Form1 will call this when the drop-down changes.
    {
        if (PersonChangedDel != null)
        {
            PersonChangedDel(p);
        }
    }
}

这是我们的用户控件:

public partial class DetailView : UserControl
{
    public DetailView()
    {
        InitializeComponent();
        Mediator.PersonChangedDel += DetailView_PersonChanged;
    }

    void DetailView_PersonChanged(Person p)
    {
        BindData(p);
    }

    public void BindData(Person p)
    {
        lblPersonHairColor.Text = p.HairColor;
        lblPersonId.Text = p.IdPerson.ToString();
        lblPersonName.Text = p.Name;
        lblPersonNickName.Text = p.NickName;

    }
}

最后,我们的Form1.cs中包含以下代码。在这里,我们正在调用OnPersonChanged,它将调用预订到委托的任何代码。

private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
    Mediator.OnPersonChanged((Person)comboBox1.SelectedItem); //Call the mediator's OnPersonChanged method. This will in turn call all the methods assigned (i.e. subscribed to) to the delegate -- in this case `DetailView_PersonChanged`.
}

好。这样便可以在不使用事件仅使用委托的情况下使此工作正常进行。我们只是将一个公共委托放入一个类中-您可以使其成为静态或单例,或其他任何形式。大。

但是,但是,我们不想做我上面刚刚描述的事情。因为公共场所有很多原因,所以很糟糕。那么我们有什么选择呢?正如John Skeet所描述的,这是我们的选择:

  1. 一个公共委托变量(这就是我们上面所做的。不要这样做。我在上面告诉过你为什么它很糟糕)
  2. 将委托放入带有get / set的属性中(这里的问题是订阅者可以互相覆盖-因此我们可以为委托订阅一堆方法,然后我们可能会不小心地说PersonChangedDel = null,抹去了所有其他订阅。剩下的另一个问题是,由于用户可以访问委托,因此他们可以调用调用列表中的目标-我们不希望外部用户有权访问何时引发事件。
  3. 具有AddXXXHandler和RemoveXXXHandler方法的委托变量

从本质上讲,这是事件给我们带来的。当我们声明一个EventHandler时,它使我们可以访问委托-而不是公开地,不作为属性访问,但由于这件事,我们将其称为仅具有添加/删除访问器的事件。

让我们看看相同的程序是什么样子,但是现在使用事件代替公共委托(我也将调解器更改为单例):

示例2:使用EventHandler代替公共委托

调解员:

class Mediator
{

    private static readonly Mediator _Instance = new Mediator();

    private Mediator() { }

    public static Mediator GetInstance()
    {
        return _Instance;
    }

    public event EventHandler<PersonChangedEventArgs> PersonChanged; //this is just a property we expose to add items to the delegate.

    public void OnPersonChanged(object sender, Person p)
    {
        var personChangedDelegate = PersonChanged as EventHandler<PersonChangedEventArgs>;
        if (personChangedDelegate != null)
        {
            personChangedDelegate(sender, new PersonChangedEventArgs() { Person = p });
        }
    }
}

注意,如果在EventHandler上按F12,它将向您显示定义只是带有额外的“发送者”对象的通用委托:

public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);

用户控件:

public partial class DetailView : UserControl
{
    public DetailView()
    {
        InitializeComponent();
        Mediator.GetInstance().PersonChanged += DetailView_PersonChanged;
    }

    void DetailView_PersonChanged(object sender, PersonChangedEventArgs e)
    {
        BindData(e.Person);
    }

    public void BindData(Person p)
    {
        lblPersonHairColor.Text = p.HairColor;
        lblPersonId.Text = p.IdPerson.ToString();
        lblPersonName.Text = p.Name;
        lblPersonNickName.Text = p.NickName;

    }
}

最后,这里是Form1.cs代码:

private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
        Mediator.GetInstance().OnPersonChanged(this, (Person)comboBox1.SelectedItem);
}

因为EventHandler希望将EventArgs作为参数,所以我创建了一个仅包含单个属性的类:

class PersonChangedEventArgs
{
    public Person Person { get; set; }
}

希望这向您展示了我们为什么会发生事件以及事件与委托人有何不同(但功能上相同)。


尽管我赞赏这篇文章中的所有出色工作,并且喜欢阅读其中的大部分内容,但我仍然感到一个问题没有解决- The other problem that remains here is that since the users have access to the delegate, they can invoke the targets in the invocation list -- we don't want external users having access to when to raise our events。在的最新版本中MediatorOnPersonChange只要有对单例的引用,您仍然可以调用。也许您应该提到,这种Mediator方法并不能阻止这种特定行为,而是更接近事件总线。
Ivaylo Slavov


6

活动与代表之间的巨大误解!!!委托指定TYPE(例如class,或interfacedos),而事件只是一种MEMBER(例如字段,属性等)。而且,就像其他任何类型的成员一样,事件也具有类型。但是,对于事件,事件类型必须由委托人指定。例如,您不能声明接口定义的类型的事件。

最后,我们可以进行以下观察:事件的类型必须由委托定义。这是事件与代表之间的主要关系,在II.18节“ 定义ECMA-335(CLI)分区I至VI的事件 ” 中进行了介绍:

在典型用法中,TypeSpec(如果存在)会标识其签名与传递给事件的fire方法的参数匹配的委托

但是,此事实并不意味着事件使用后备委托字段。实际上,事件可以使用您选择的任何其他数据结构类型的支持字段。如果您在C#中显式实现一个事件,则可以自由选择存储事件处理程序的方式(请注意,事件处理程序该事件类型的实例,而从先前的观察中,该类型又是强制类型委托类型))。但是,您可以将这些事件处理程序(它们是委托实例)存储在诸如a 或a 或其他任何其他数据结构中,甚至存储在支持委托字段中。但是请不要忘记使用委托字段不是强制性的。ListDictionary


4

.net中的事件是Add方法和Remove方法的指定组合,两者都期望某种特定类型的委托。C#和vb.net都可以自动为add和remove方法生成代码,这将定义一个委托来保存事件订阅,并向该订阅委托中添加传入的委托或从中删除该传入的委托。VB.net也将自动生成代码(带有RaiseEvent语句),以在且仅当该列表为非空时才调用该订阅列表。由于某种原因,C#不会生成后者。

请注意,虽然通常使用多播委托来管理事件订阅,但这并不是这样做的唯一方法。从公众的角度来看,可能的事件订阅者需要知道如何让对象知道其想要接收事件,但是它不需要知道发布者将使用哪种机制引发事件。还要注意,尽管在.net中定义事件数据结构的人显然认为应该有一种公开的方法来提高它们,但C#和vb.net均未使用该功能。


3

要以简单方式定义about事件:

活动是对代表的引用,有两个限制

  1. 不能直接调用
  2. 无法直接分配值(例如eventObj =委托方法)

以上两个是代表们的薄弱环节,这一点在最终得到解决。此处显示了提琴手的不同之处的完整代码示例为https://dotnetfiddle.net/5iR3fB

切换“事件”和“委托”之间的注释以及调用/分配值以委托以了解差异的客户端代码

这是内联代码。

 /*
This is working program in Visual Studio.  It is not running in fiddler because of infinite loop in code.
This code demonstrates the difference between event and delegate
        Event is an delegate reference with two restrictions for increased protection

            1. Cannot be invoked directly
            2. Cannot assign value to delegate reference directly

Toggle between Event vs Delegate in the code by commenting/un commenting the relevant lines
*/

public class RoomTemperatureController
{
    private int _roomTemperature = 25;//Default/Starting room Temperature
    private bool _isAirConditionTurnedOn = false;//Default AC is Off
    private bool _isHeatTurnedOn = false;//Default Heat is Off
    private bool _tempSimulator = false;
    public  delegate void OnRoomTemperatureChange(int roomTemperature); //OnRoomTemperatureChange is a type of Delegate (Check next line for proof)
    // public  OnRoomTemperatureChange WhenRoomTemperatureChange;// { get; set; }//Exposing the delegate to outside world, cannot directly expose the delegate (line above), 
    public  event OnRoomTemperatureChange WhenRoomTemperatureChange;// { get; set; }//Exposing the delegate to outside world, cannot directly expose the delegate (line above), 

    public RoomTemperatureController()
    {
        WhenRoomTemperatureChange += InternalRoomTemperatuerHandler;
    }
    private void InternalRoomTemperatuerHandler(int roomTemp)
    {
        System.Console.WriteLine("Internal Room Temperature Handler - Mandatory to handle/ Should not be removed by external consumer of ths class: Note, if it is delegate this can be removed, if event cannot be removed");
    }

    //User cannot directly asign values to delegate (e.g. roomTempControllerObj.OnRoomTemperatureChange = delegateMethod (System will throw error)
    public bool TurnRoomTeperatureSimulator
    {
        set
        {
            _tempSimulator = value;
            if (value)
            {
                SimulateRoomTemperature(); //Turn on Simulator              
            }
        }
        get { return _tempSimulator; }
    }
    public void TurnAirCondition(bool val)
    {
        _isAirConditionTurnedOn = val;
        _isHeatTurnedOn = !val;//Binary switch If Heat is ON - AC will turned off automatically (binary)
        System.Console.WriteLine("Aircondition :" + _isAirConditionTurnedOn);
        System.Console.WriteLine("Heat :" + _isHeatTurnedOn);

    }
    public void TurnHeat(bool val)
    {
        _isHeatTurnedOn = val;
        _isAirConditionTurnedOn = !val;//Binary switch If Heat is ON - AC will turned off automatically (binary)
        System.Console.WriteLine("Aircondition :" + _isAirConditionTurnedOn);
        System.Console.WriteLine("Heat :" + _isHeatTurnedOn);

    }

    public async void SimulateRoomTemperature()
    {
        while (_tempSimulator)
        {
            if (_isAirConditionTurnedOn)
                _roomTemperature--;//Decrease Room Temperature if AC is turned On
            if (_isHeatTurnedOn)
                _roomTemperature++;//Decrease Room Temperature if AC is turned On
            System.Console.WriteLine("Temperature :" + _roomTemperature);
            if (WhenRoomTemperatureChange != null)
                WhenRoomTemperatureChange(_roomTemperature);
            System.Threading.Thread.Sleep(500);//Every second Temperature changes based on AC/Heat Status
        }
    }

}

public class MySweetHome
{
    RoomTemperatureController roomController = null;
    public MySweetHome()
    {
        roomController = new RoomTemperatureController();
        roomController.WhenRoomTemperatureChange += TurnHeatOrACBasedOnTemp;
        //roomController.WhenRoomTemperatureChange = null; //Setting NULL to delegate reference is possible where as for Event it is not possible.
        //roomController.WhenRoomTemperatureChange.DynamicInvoke();//Dynamic Invoke is possible for Delgate and not possible with Event
        roomController.SimulateRoomTemperature();
        System.Threading.Thread.Sleep(5000);
        roomController.TurnAirCondition (true);
        roomController.TurnRoomTeperatureSimulator = true;

    }
    public void TurnHeatOrACBasedOnTemp(int temp)
    {
        if (temp >= 30)
            roomController.TurnAirCondition(true);
        if (temp <= 15)
            roomController.TurnHeat(true);

    }
    public static void Main(string []args)
    {
        MySweetHome home = new MySweetHome();
    }


}

2

委托是类型安全的函数指针。事件是使用委托的发布者-订阅者设计模式的实现。


0

如果您检查中级语言,您将知道.net编译器使用一些内置函数将委托转换为IL中的密封类,例如一些从另一个类(可能称为“ SystemMulticast”)继承的内置函数,例如invoke,beginInvoke,endInvoke和委托类。我猜Event是Delegate的子类,带有一些其他属性。

事件实例和委托实例之间的区别是,您不能在声明之外运行事件。如果在类A中声明一个事件,则只能在类A中运行此事件。如果在类A中声明一个委托,则可以在任何地方使用此委托。我认为这是他们之间的主要区别

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.