代表和活动之间有什么区别?两者都不能保存对可以执行的函数的引用吗?
一个事件的声明增加了抽象和保护上一层委托实例.此保护可防止委托的客户端重置委托及其调用列表,并仅允许从调用列表中添加或删除目标.
要了解这些差异,您可以看看这2个示例
代表示例(在本例中为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#中使用.
您的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 EventHandlerRun = 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();
区别:
您没有使用公共属性,而是使用公共字段(使用事件,编译器保护您的字段免受不必要的访问)
无法直接分配事件.在这种情况下,它不会引起我在覆盖行为时显示的先前错误.
班上没有人可以提出这个活动.
事件可以包含在接口声明中,而字段则不能
笔记:
EventHandler被声明为以下委托:
public delegate void EventHandler (object sender, EventArgs e)
它需要一个发送者(对象类型)和事件参数.如果发件人来自静态方法,则它为null.
这个使用的例子EventHandler
也可以用来EventHandler
代替.
有关EventHandler的文档,请参阅此处
除了语法和操作属性之外,还存在语义差异.
从概念上讲,代表是功能模板; 也就是说,它们表达了一个函数必须遵守的契约,以便被认为是代表的"类型".
事件代表......好吧,事件.它们旨在在某些事情发生时提醒某人,是的,它们遵循代理定义,但它们不是一回事.
即使它们完全相同(语法和IL代码),仍然会保留语义差异.一般来说,我更喜欢为两个不同的概念设置两个不同的名称,即使它们以相同的方式实现(这并不意味着我喜欢两次使用相同的代码).
这是另一个很好的链接. http://csharpindepth.com/Articles/Chapter2/Events.aspx
简而言之,从文章中删除 - 事件是对代表的封装.
文章引用:
假设事件在C#/ .NET中不作为概念存在.另一个班级如何订阅活动?三种选择:
公共委托变量
由属性支持的委托变量
具有AddXXXHandler和RemoveXXXHandler方法的委托变量
选项1显然是可怕的,因为我们厌恶公共变量的所有正常原因.
选项2略胜一筹,但允许订阅者有效地互相覆盖 - 编写someInstance.MyEvent = eventHandler会很容易; 它将替换任何现有的事件处理程序,而不是添加新的事件处理程序.此外,您仍需要编写属性.
选项3基本上是事件给你的东西,但有一个保证的约定(由编译器生成并由IL中的额外标志支持)和一个"自由"实现,如果你对字段式事件给你的语义感到满意.订阅和取消订阅事件是封装的,不允许任意访问事件处理程序列表,语言可以通过提供声明和订阅的语法使事情变得更简单.
注意:如果您有权访问C#5.0 Unleashed,请阅读第18章"事件"中的"对代理人的简单使用的限制",以更好地理解两者之间的差异.
有一个简单,具体的例子总能帮助我.所以这是社区的一个.首先,我将展示如何单独使用代表来完成事件为我们所做的事情.然后我展示了同一个解决方案如何与一个实例一起工作EventHandler
.然后我解释为什么我们不想做我在第一个例子中解释的内容.这篇文章的灵感来自John Skeet 的一篇文章.
示例1:使用公共委托
假设我有一个带有单个下拉框的WinForms应用程序.下拉列表必然会出现问题List
.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所描述的,以下是我们的选择:
一个公共委托变量(这是我们上面刚刚做过的.不要这样做.我刚才告诉你为什么它很糟糕)
将委托放入带有get/set的属性中(这里的问题是订阅者可以互相覆盖 - 所以我们可以向委托订阅一堆方法,然后我们可能会意外地说PersonChangedDel = null
,消除所有其他订阅.此处存在的另一个问题是,由于用户可以访问委托,因此他们可以调用调用列表中的目标 - 我们不希望外部用户有权访问何时提升我们的事件.
具有AddXXXHandler和RemoveXXXHandler方法的委托变量
第三种选择基本上是一个事件给我们的东西.当我们声明一个EventHandler时,它允许我们访问一个委托 - 不是公开的,不是作为属性,但是我们称之为只添加/删除访问者的事件.
让我们看看相同的程序是什么样的,但现在使用Event代替公共委托(我还将我们的Mediator更改为单例):
示例2:使用EventHandler而不是公共委托
中保
class Mediator { private static readonly Mediator _Instance = new Mediator(); private Mediator() { } public static Mediator GetInstance() { return _Instance; } public event EventHandlerPersonChanged; //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 ; if (personChangedDelegate != null) { personChangedDelegate(sender, new PersonChangedEventArgs() { Person = p }); } } }
请注意,如果您在EventHandler上使用F12,它将向您显示定义只是一个带有额外"sender"对象的通用ified委托:
public delegate void EventHandler(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(例如a class
或a interface
),而事件只是一种成员(例如字段,属性等).并且,就像任何其他类型的成员一样,事件也有类型.但是,在事件的情况下,事件的类型必须由委托指定.例如,您不能声明由接口定义的类型的事件.
最后,我们可以进行以下观察:事件的类型必须由委托定义.这是一个事件和委托之间的主要关系,并在部分中描述II.18定义事件的ECMA-335(CLI)分区I至VI:
在典型用法中,TypeSpec(如果存在)标识一个委托,其签名与传递给事件的fire方法的参数匹配.
但是,这一事实并不意味着事件使用了支持委托字段.实际上,事件可能使用您选择的任何不同数据结构类型的支持字段.如果在C#中显式实现事件,则可以自由选择存储事件处理程序的方式(请注意,事件处理程序是事件类型的实例,而事件处理程序又强制性地是委托类型 ---来自之前的观察).但是,您可以将这些事件处理程序(它们是委托实例)存储在数据结构中,例如a List
或a Dictionary
或任何其他数据结构,甚至可以存储在支持委托字段中.但不要忘记,您不必使用委托字段.