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

委托和事件之间的区别是什么?两者不都持有可以执行的函数的引用吗?

148851 次浏览

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

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

从概念上讲,委托是函数模板;也就是说,它们表达了一个函数必须遵守的契约,以便被认为是委托的“类型”。

事件代表……嗯,事件。它们的目的是在发生事情时提醒某人,是的,它们遵循委托定义,但它们不是同一件事。

即使它们是完全相同的东西(在语法上和IL代码中),仍然会存在语义差异。一般来说,我更喜欢对两个不同的概念使用两个不同的名称,即使它们以相同的方式实现(这并不意味着我喜欢使用两次相同的代码)。

您还可以在接口声明中使用事件,但委托则不是这样。

.net中的事件是Add方法和Remove方法的指定组合,两者都需要某种特定类型的委托。c#和vb.net都可以自动生成添加和删除方法的代码,这些方法将定义一个委托来保存事件订阅,并将传入的委托添加/删除到该订阅委托。VB.net还将自动生成代码(使用RaiseEvent语句),当且仅当订阅列表非空时调用订阅列表;由于某些原因,c#不能生成后者。

请注意,虽然使用多播委托管理事件订阅很常见,但这并不是唯一的方法。从公共的角度来看,可能的事件订阅者需要知道如何让对象知道它希望接收事件,但不需要知道发布者将使用什么机制来引发事件。还要注意的是,尽管在。net中定义事件数据结构的人显然认为应该有一种公共的方式来引发它们,但c#和vb.net都没有使用这个特性。

这是另一个很好的参考链接。 http://csharpindepth.com/Articles/Chapter2/Events.aspx < / p >

简单地说,本文的要点是——事件是对委托的封装。

引用自文章:

假设在c# /. net中没有事件这个概念。另一个类如何订阅事件?三个选项:

  1. 一个公共委托变量

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

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

选项1显然很糟糕,因为我们厌恶公共变量的所有正常原因。

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

选项3基本上是事件给你的,但是有一个有保证的约定(由编译器生成,并由IL中的额外标志支持)和一个“免费”实现,如果你对类字段事件给你的语义满意的话。订阅和取消订阅事件被封装在不允许任意访问事件处理程序列表的情况下,并且语言可以通过为声明和订阅提供语法使事情变得更简单。

为了理解它们的区别,你可以看看这两个例子

委托的示例(在本例中是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类的类都可以直接调用委托。例如,animal.Run()animal.Run.Invoke()在Animal类之外有效。

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

你的动物职业会这样改变:

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. 类之外的任何人都不能引发或调用该事件。例如,animal.Run()animal.Run.Invoke()在Animal类之外无效,会产生编译器错误。
  4. 事件可以包含在接口声明中,而字段则不能

注:

EventHandler被声明为如下委托:

public delegate void EventHandler (object sender, EventArgs e)

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

这个例子使用了EventHandler<ArgsSpecial>,也可以用EventHandler来代替。

有关EventHandler的文档请参阅在这里

活动和代表之间的误解太大了!!委托指定TYPE(如classinterface),而事件只是一种成员(如字段、属性等)。并且,就像任何其他类型的成员一样,事件也有类型。然而,在事件的情况下,事件的类型必须由委托指定。例如,不能声明由接口定义的类型的事件。

最后,我们可以使下面的观察:事件的类型必须由委托定义。这是事件和委托之间的主要关系,在ECMA-335 (CLI)分区I到VIII.18定义事件节中描述:

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

然而,这一事实并不意味着事件使用了支持委托字段。事实上,事件可以使用您选择的任何不同数据结构类型的支持字段。如果你显式地在c#中实现一个事件,你可以自由选择存储事件处理程序的方式(注意,事件处理程序事件类型的实例,而事件类型又必须是委托类型——来自之前的观察)。但是,您可以将这些事件处理程序(即委托实例)存储在诸如ListDictionary或任何其他数据结构中,甚至存储在支持委托字段中。但是不要忘记,使用委托字段不是强制性的。

注意:如果你可以访问c# 5.0发布,请阅读第18章“事件”中的“delegate的普通使用限制”,以更好地理解两者之间的区别。


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

例1:使用公共委托

假设我有一个只有一个下拉框的WinForms应用程序。下拉列表绑定到List<Person>。其中Person具有Id,名称,昵称,头发颜色的属性。在主窗体上有一个自定义用户控件,显示此人的属性。当某人在下拉菜单中选择某人时,用户控件中的标签将更新以显示所选人员的属性。

enter image description here

下面是它的工作原理。我们有三个文件可以帮助我们把这些放在一起:

  • cs——静态类保存委托
  • Form1.cs——主要形式
  • 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`.
}

好的。这就是你如何让它工作的不使用事件只是使用委托。我们只是把一个公共委托放到一个类中——你可以让它是静态的,或者是单例的,等等。太好了。

但是,但是,但是,我们不想做我上面描述的事情。因为公共字段不好有很多很多原因。那么我们有什么选择呢?正如约翰·斯基特所描述的,我们有以下几种选择:

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

第三个选项本质上是事件给我们的。当我们声明一个EventHandler时,它给了我们对委托的访问,不是公开的,不是作为属性,而是作为我们称之为事件的东西它有添加/删除访问器。

让我们看看同样的程序是什么样子,但是现在使用Event而不是public委托(我也将我们的Mediator更改为单例):

例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,它会显示你的定义只是一个泛型的委托,带有额外的"sender"对象:

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

希望这向您展示了为什么我们有事件,以及它们作为委托如何不同——但功能相同。

用简单的方式定义about event:

事件是具有两个限制的委托的参考

  1. 不能直接调用
  2. 不能直接赋值(例如eventObj = delegateMethod)

以上两点是委托的弱点,并在事件中得到解决。完整的代码示例显示fiddler的差异在这里https://dotnetfiddle.net/5iR3fB

在Event和Delegate之间切换注释,以及调用/分配值给Delegate的客户端代码,以了解差异

下面是内联代码。

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




}

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

对于生活在2020年的人们来说,想要一个干净的答案……

定义:

  • delegate:定义一个函数指针。
  • <李> event:定义
    • (1) 受保护的接口,而且
    • (2)操作(+=-=), 而且
    • (3)优点:你不再需要使用new关键字。

关于形容词受保护的:

// eventTest.SomeoneSay = null;              // Compile Error.
// eventTest.SomeoneSay = new Say(SayHello); // Compile Error.

还要注意来自Microsoft的这个部分:https://learn.microsoft.com/en-us/dotnet/standard/events/#raising-multiple-events

代码示例:

delegate:

public class DelegateTest
{
public delegate void Say(); // Define a pointer type "void <- ()" named "Say".
private Say say;


public DelegateTest() {
say  = new Say(SayHello);     // Setup the field, Say say, first.
say += new Say(SayGoodBye);
        

say.Invoke();
}
    

public void SayHello() { /* display "Hello World!" to your GUI. */ }
public void SayGoodBye() { /* display "Good bye!" to your GUI. */ }
}

event:

public class EventTest
{
public delegate void Say();
public event Say SomeoneSay;  // Use the type "Say" to define event, an
// auto-setup-everything-good field for you.
public EventTest() {
SomeoneSay += SayHello;
SomeoneSay += SayGoodBye;


SomeoneSay();
}
    

public void SayHello() { /* display "Hello World!" to your GUI. */ }
public void SayGoodBye() { /* display "Good bye!" to your GUI. */ }
}

参考:

__abc0 - __abc1: __abc2