委托和事件之间的区别是什么?两者不都持有可以执行的函数的引用吗?
当前回答
您还可以在接口声明中使用事件,但委托则不是这样。
其他回答
注意:如果你有c# 5.0 Unleashed,请阅读第18章“事件”中的“委托的普通使用限制”,以更好地理解两者之间的区别。
举一个简单而具体的例子总是对我有帮助。这是给社区的。首先,我将展示如何单独使用委托来完成事件为我们做的事情。然后,我将展示相同的解决方案如何使用EventHandler的实例。然后我解释为什么我们不想做我在第一个例子中解释的事情。这篇文章的灵感来自John Skeet的一篇文章。
例1:使用公共委托
假设我有一个只有一个下拉框的WinForms应用程序。下拉列表绑定到List<Person>。其中Person具有Id,名称,昵称,头发颜色的属性。在主窗体上有一个自定义用户控件,显示此人的属性。当某人在下拉菜单中选择某人时,用户控件中的标签将更新以显示所选人员的属性。
下面是它的工作原理。我们有三个文件可以帮助我们把这些放在一起:
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`.
}
好的。这就是不使用事件只使用委托就能让它工作的方法。我们只是把一个公共委托放到一个类中——你可以让它是静态的,或者是单例的,等等。太好了。
但是,但是,但是,我们不想做我上面描述的事情。因为公共字段有很多不好的原因。那么我们有什么选择呢?正如约翰·斯基特所描述的,我们有以下几种选择:
A public delegate variable (this is what we just did above. don't do this. i just told you above why it's bad) Put the delegate into a property with a get/set (problem here is that subscribers could override each other -- so we could subscribe a bunch of methods to the delegate and then we could accidentally say PersonChangedDel = null, wiping out all of the other subscriptions. 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. A delegate variable with AddXXXHandler and RemoveXXXHandler methods
第三个选项本质上是事件给我们的。当我们声明一个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; }
}
希望这向您展示了为什么我们有事件,以及它们作为委托如何不同——但功能相同。
活动和代表之间的误解太大了!!委托指定TYPE(例如类或接口),而事件只是一种成员(例如字段、属性等)。并且,就像任何其他类型的成员一样,事件也有类型。然而,在事件的情况下,事件的类型必须由委托指定。例如,不能声明由接口定义的类型的事件。
最后,我们可以做以下观察:事件的类型必须由委托定义。这是事件和委托之间的主要关系,在第二节中描述。18定义ECMA-335 (CLI)分区I到VI的事件:
在典型用法中,TypeSpec(如果存在)标识一个签名与传递给事件的fire方法的参数匹配的委托。
However, this fact does NOT imply that an event uses a backing delegate field. In truth, an event may use a backing field of any different data structure type of your choice. If you implement an event explicitly in C#, you are free to choose the way you store the event handlers (note that event handlers are instances of the type of the event, which in turn is mandatorily a delegate type---from the previous Observation). But, you can store those event handlers (which are delegate instances) in a data structure such as a List or a Dictionary or any other else, or even in a backing delegate field. But don’t forget that it is NOT mandatory that you use a delegate field.
An event in .net is a designated combination of an Add method and a Remove method, both of which expect some particular type of delegate. Both C# and vb.net can auto-generate code for the add and remove methods which will define a delegate to hold the event subscriptions, and add/remove the passed in delegagte to/from that subscription delegate. VB.net will also auto-generate code (with the RaiseEvent statement) to invoke the subscription list if and only if it is non-empty; for some reason, C# doesn't generate the latter.
Note that while it is common to manage event subscriptions using a multicast delegate, that is not the only means of doing so. From a public perspective, a would-be event subscriber needs to know how to let an object know it wants to receive events, but it does not need to know what mechanism the publisher will use to raise the events. Note also that while whoever defined the event data structure in .net apparently thought there should be a public means of raising them, neither C# nor vb.net makes use of that feature.
用简单的方式定义about event:
事件是对具有两个限制的委托的引用
不能直接调用 不能直接赋值(例如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();
}
}
除了语法和操作属性之外,还有语义上的差异。
从概念上讲,委托是函数模板;也就是说,它们表达了一个函数必须遵守的契约,以便被认为是委托的“类型”。
事件代表……嗯,事件。它们的目的是在发生事情时提醒某人,是的,它们遵循委托定义,但它们不是同一件事。
即使它们是完全相同的东西(在语法上和IL代码中),仍然会存在语义差异。一般来说,我更喜欢对两个不同的概念使用两个不同的名称,即使它们以相同的方式实现(这并不意味着我喜欢使用两次相同的代码)。
推荐文章
- dynamic (c# 4)和var之间的区别是什么?
- Visual Studio: ContextSwitchDeadlock
- 返回文件在ASP。Net Core Web API
- 自定义HttpClient请求头
- 如果我使用OWIN Startup.cs类并将所有配置移动到那里,我是否需要一个Global.asax.cs文件?
- VS2013外部构建错误"error MSB4019: The imported project <path> was not found"
- 从另一个列表id中排序一个列表
- 等待一个无效的异步方法
- 无法加载文件或程序集…参数不正确
- c#中枚举中的方法
- 如何从字符串中删除新的行字符?
- 如何设置一个默认值与Html.TextBoxFor?
- 检查属性是否有属性
- 格式化XML字符串以打印友好的XML字符串
- 返回内容与IHttpActionResult非ok响应