代表和事件之间有什么区别?不是都持有对可以执行的函数的引用吗?
事件声明在委托实例上添加了一层抽象和保护。此保护可防止委托的客户端重置委托及其调用列表,并且只允许在调用列表中添加或删除目标。
要了解差异,您可以查看这 2 个示例
委托示例(在这种情况下,一个动作 - 这是一种不返回值的委托)
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
。
你的 Animal 类会以这种方式改变:
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();
差异:
您没有使用公共属性,而是使用公共字段(使用事件,编译器会保护您的字段免受不必要的访问)无法直接分配事件。在这种情况下,它不会引起我在覆盖行为时显示的先前错误。班级以外的任何人都不能引发或调用该事件。例如,animal.Run() 或 animal.Run.Invoke() 在 Animal 类之外是无效的,会产生编译错误。事件可以包含在接口声明中,而字段不能
笔记:
EventHandler 被声明为以下委托:
public delegate void EventHandler (object sender, EventArgs e)
它需要一个发送者(对象类型)和事件参数。如果发送者来自静态方法,则发送者为空。
这个使用 EventHandler<ArgsSpecial>
的示例也可以改用 EventHandler
来编写。
有关 EventHandler 的文档,请参阅 here
animal
实例,任何人都不能调用 RaiseEvent
?
animal.Run(this, new ArgsSpecial("Run faster");
?
除了句法和操作属性之外,还有语义上的差异。
从概念上讲,委托是功能模板;也就是说,它们表达了一个功能必须遵守的合同,才能被认为是委托的“类型”。
事件代表……嗯,事件。它们旨在在发生某些事情时提醒某人,是的,它们遵守委托定义,但它们不是同一回事。
即使它们完全相同(在语法上和在 IL 代码中),语义上的差异仍然存在。一般来说,我更喜欢为两个不同的概念使用两个不同的名称,即使它们以相同的方式实现(这并不意味着我喜欢使用相同的代码两次)。
这是另一个可供参考的好链接。 http://csharpindepth.com/Articles/Chapter2/Events.aspx
简而言之,从文章中得出的结论——事件是对代表的封装。
引用文章:
假设事件在 C#/.NET 中不作为概念存在。另一个班级将如何订阅一个事件?三个选项: 公共委托变量 由属性支持的委托变量 具有 AddXXXHandler 和 RemoveXXXHandler 方法的委托变量 选项 1 显然是可怕的,出于我们厌恶公共变量的所有正常原因。选项 2 稍微好一点,但允许订阅者有效地相互覆盖——写 someInstance.MyEvent = eventHandler; 太容易了。这将替换任何现有的事件处理程序,而不是添加一个新的。此外,您还需要编写属性。选项 3 基本上是事件给你的东西,但是如果你对类似字段的事件给你的语义感到满意,那么它有保证的约定(由编译器生成并由 IL 中的额外标志支持)和“免费”实现。订阅和取消订阅事件被封装,不允许任意访问事件处理程序列表,并且语言可以通过为声明和订阅提供语法来使事情变得更简单。
public Delegate
变量会暴露“数据”,但据我所知,OOP 从未提及任何类似于 Delegate
的概念(它既不是“对象”也不是“消息”),而且 .NET 确实几乎没有将委托视为数据。
private Delegate
变量创建自己的 AddXXXHandler
方法可能是一个不错的选择。在这种情况下,您可以检查是否已经设置了处理程序,并做出适当的反应。如果您需要持有 Delegate
的对象能够清除所有处理程序(event
没有给您任何方法来执行此操作),这也可能是一个很好的设置。
事件和代表之间有多么大的误解!!!委托指定了一种类型(例如 class
或 interface
),而事件只是一种成员(例如字段、属性等)。而且,就像任何其他类型的成员一样,事件也有一个类型。然而,在事件的情况下,事件的类型必须由委托指定。例如,您不能声明由接口定义的类型的事件。
最后,我们可以进行以下观察:事件的类型必须由委托定义。这是事件和委托之间的主要关系,在 ECMA-335 (CLI) Partitions I to VI 的 II.18 定义事件部分中进行了描述:
在典型用法中,TypeSpec(如果存在)标识其签名与传递给事件的 fire 方法的参数匹配的委托。
但是,这一事实并不意味着事件使用支持委托字段。事实上,一个事件可以使用您选择的任何不同数据结构类型的支持字段。如果您在 C# 中显式实现事件,则可以自由选择存储 事件处理程序 的方式(请注意,事件处理程序是 事件,它又是强制的委托类型---来自上一个Observation)。但是,您可以将这些事件处理程序(它们是委托实例)存储在诸如 List
或 Dictionary
或任何其他数据结构中,甚至可以存储在支持委托字段中。但不要忘记使用委托字段不是强制性的。
注意:如果您有权访问 C# 5.0 Unleashed,请阅读第 18 章标题为“事件”的“简单使用委托的限制”,以更好地了解两者之间的区别。
有一个简单而具体的例子总是对我有帮助。所以这是社区的一个。首先,我展示了如何单独使用委托来完成事件为我们所做的事情。然后我将展示相同的解决方案如何与 EventHandler
的实例一起使用。然后我解释了为什么我们不想做我在第一个例子中解释的事情。这篇文章的灵感来自 John Skeet 的 an article。
示例 1:使用公共委托
假设我有一个带有单个下拉框的 WinForms 应用程序。下拉列表绑定到 List<Person>
。其中 Person 具有 Id、Name、NickName、HairColor 的属性。在主窗体上是一个自定义用户控件,显示该人的属性。当有人在下拉列表中选择一个人时,用户控件中的标签会更新以显示所选人员的属性。
https://i.stack.imgur.com/gfk5j.png
这是它的工作原理。我们有三个文件可以帮助我们把它放在一起:
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`.
}
好的。所以这就是你如何在不使用事件和只使用委托的情况下完成这项工作。我们只是将一个公共委托放入一个类中——您可以将其设为静态或单例,或其他任何方式。伟大的。
但是,但是,但是,我们不想做我刚才描述的事情。因为public fields are bad有很多很多的原因。那么我们有哪些选择呢?正如 John Skeet 所描述的,以下是我们的选择:
一个公共委托变量(这是我们刚刚在上面所做的。不要这样做。我刚刚在上面告诉过你为什么它不好)将委托放入带有 get/set 的属性中(这里的问题是订阅者可以相互覆盖 - - 所以我们可以为委托订阅一堆方法,然后我们可能会不小心说 PersonChangedDel = null,清除所有其他订阅。这里剩下的另一个问题是,由于用户可以访问委托,他们可以调用调用列表中的目标——我们不希望外部用户有权访问何时引发我们的事件。具有 AddXXXHandler 和 RemoveXXXHandler 方法的委托变量
这第三个选项本质上是事件给我们的。当我们声明一个 EventHandler 时,它使我们能够访问一个委托——不是公开的,不是作为属性,而是作为这个东西,我们称之为一个只有添加/删除访问器的事件。
让我们看看同一个程序是什么样子,但现在使用事件而不是公共委托(我也将我们的 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; }
}
希望这能向您展示我们为什么有事件以及它们与代表有何不同——但在功能上是相同的——。
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
。在最新版本的 Mediator
中,只要引用了单例,您仍然可以调用 OnPersonChange
。也许您应该提到 Mediator
方法不会阻止该特定行为,并且更接近事件总线。
您还可以在接口声明中使用事件,而不是委托。
Action a { get; set; }
。
.net 中的事件是 Add 方法和 Remove 方法的指定组合,这两种方法都需要某种特定类型的委托。 C# 和 vb.net 都可以为 add 和 remove 方法自动生成代码,这些方法将定义一个委托来保存事件订阅,并向该订阅委托添加/删除传入的委托。当且仅当订阅列表非空时,VB.net 也会自动生成代码(使用 RaiseEvent 语句)来调用订阅列表;由于某种原因,C# 不会生成后者。
请注意,虽然使用多播委托管理事件订阅很常见,但这并不是唯一的方法。从公众的角度来看,潜在的事件订阅者需要知道如何让对象知道它想要接收事件,但它不需要知道发布者将使用什么机制来引发事件。另请注意,尽管在 .net 中定义事件数据结构的人显然认为应该有一种公开的方法来提升它们,但 C# 和 vb.net 都没有使用该功能。
委托是一个类型安全的函数指针。事件是使用委托的发布者 - 订阅者设计模式的实现。
以简单的方式定义关于事件:
事件是对具有两个限制的委托的引用
不能直接调用不能直接赋值(例如eventObj = delegateMethod)
以上两个是代表的弱点,并在事件中解决。显示提琴手差异的完整代码示例在这里 https://dotnetfiddle.net/5iR3fB 。
在 Event 和 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();
}
}
对于生活在 2020 年的人们来说,想要一个干净的答案......
定义:
委托:定义一个函数指针。
event:定义(1)受保护的接口,(2)操作(+=,-=),以及(3)优点:您不再需要使用 new 关键字。
(1) 受保护的接口,以及
(2) 操作(+=, -=),和
(3) 优点:不再需要使用 new 关键字。
关于受保护的形容词:
// eventTest.SomeoneSay = null; // Compile Error.
// eventTest.SomeoneSay = new Say(SayHello); // Compile Error.
另请注意 Microsoft 的此部分:https://docs.microsoft.com/en-us/dotnet/standard/events/#raising-multiple-events
代码示例:
与代表:
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. */ }
}
有事件:
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. */ }
}
参考:
Event 与 Delegate - 解释 C# 中 Event 和 Delegate 模式之间的重要区别以及它们为何有用。:https://dzone.com/articles/event-vs-delegate