[译]C#事件实施基础知识,最佳做法和惯例
By robot-v1.0
本文链接 https://www.kyfws.com/best-practices/c-event-implementation-fundamentals-best-practices-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 90 分钟阅读 - 44661 个词 阅读量 0[译]C#事件实施基础知识,最佳做法和惯例
原文地址:https://www.codeproject.com/Articles/20550/C-Event-Implementation-Fundamentals-Best-Practices
原文作者:Jeffrey Schaefer
译文由本站 robot-v1.0 翻译
前言
This article presents event implementation fundamentals, best practices, and conventions. 本文介绍了事件实现的基础知识,最佳实践和约定.
介绍(Introduction)
本文介绍了使用C#定义,实现和理解自定义事件所需的一切.为了实现这些目标,除了事件实现的最佳实践和约定之外,还介绍了必须或应该利用的基本构建块.本文介绍了用于发布和订阅事件的.NET 1.x和2.0+替代方案.(Everything you need to define, implement, and understand custom events using C# is presented in this article. Toward accomplishing these objectives the fundamental building blocks that must or should be utilized are presented, in addition to event implementation best practices and conventions. This article presents both .NET 1.x and 2.0+ alternatives for publishing and subscribing to events.)
自.NET Framework 1.0版以来,已经提供了对自定义事件的实现的支持,但此后添加了与事件相关的其他支持和功能.一些新功能(例如通用(While support for the implementation of custom events has been available since the 1.0 version of the .NET Framework, additional event-related support and capabilities have been added since then. Some of the new capabilities (e.g., generic) System.EventHandler
,匿名方法,委托推断等)组成旨在简化事件实现的快捷方式.尽管此类技术确实有助于更快地实现事件,但在基本构建块之前或代替它们进行呈现会产生不太明确的呈现.因此,在引入基本构建块之前,本文将避免此类捷径.(, anonymous methods, delegate inference, etc.) comprise shortcuts intended to make the implementation of events easier. While such techniques do facilitate quicker event implementations, presenting them before or in place of the fundamental building blocks would yield a less explicit presentation. Consequently, this article avoids such shortcuts until after the fundamental building blocks have been introduced.)
内容(Contents)
- 关于受众的假设(Assumptions About the Audience)
- 术语和定义(Terminology and Definitions)
- 代表们(Delegates)
- 代表与活动的关系(The Relationship Between Delegates and Events)
- 事件参数(EventArgs)(Event Arguments (EventArgs))
- 事件声明语法(Event Declaration Syntax)
- 活动筹码(Event Raising Code)
- 事件订阅者注册和注销(Event Subscriber Registration and Unregistration)
- 事件处理方法(Event Handling Method)
- .NET 1.x与2.0+注意事项(.NET 1.x vs. 2.0+ Considerations)
- 10.1泛型(10.1 Generics)
- [10.2委托推理(10.2 Delegate Inference)](https://www.codeproject.com#10.2Delegate Inference)
- 10.3匿名方法(10.3 Anonymous Methods)
- 10.4局部类(10.4 Partial Classes)
- 约定(Conventions)
- 创建自定义事件的步骤(Steps to Creating Custom Events)
- 样本事件实现(Sample Event Implementation)
- 处理.NET Framework组件引发的事件-演练和示例(Handling Events Raised by .NET Framework Components - Walkthrough and Example)
- Windows窗体事件(Windows Forms Events)
- 15.1 NET 1.x和2.0+的区别-局部类(15.1 NET 1.x and 2.0+ Difference - Partial Classes)
- 15.2事件的部分类和Windows Forms Designer注意事项(15.2 Partial Classes and Windows Forms Designer Considerations For Events)
- 15.3演练-处理Windows窗体事件(15.3 Walkthrough - Handling a Windows Forms Event)
- 15.4 Windows窗体和线程注意事项(15.4 Windows Forms and Threading Considerations)
- 可取消的活动(Cancellable Events)
- ASP.NET Web窗体事件(ASP.NET Web Forms Events)
- 资料来源(Sources)
- 历史(History)
1.关于受众的假设(1. Assumptions About the Audience)
除了对Generic的理解之外,本文还假定您具有C#的.NET编程工作知识,这些知识是在.NET Framework 2.0版中引入的.如果您不理解泛型,那么本文仍然会有所帮助,因为有些方法可以实现不依赖泛型的事件.本文介绍了通用和非通用事件实现技术.(This article assumes a working knowledge of .NET programming with C#, in addition to an understanding of Generics, which were introduced in the 2.0 version of the .NET Framework. If you do not understand Generics, this article can still be helpful as there are ways to implement events that do not rely on Generics. Both generic and non-generic event implementation techniques are presented in this article.)
2.术语和定义(2. Terminology and Definitions)
呈现事件和相关概念的文献经常使用多个单词或表达来描述任何给定的概念.下表列出了该术语的大部分内容,并对表达式背后的概念进行了简要说明.(The literature presenting events and related concepts frequently makes use of multiple words or expressions to describe any given concept. The following list catalogs much of this terminology with brief explanations of the concepts behind the expressions.)
事件,事件前,事件后和状态,状态变化以及状态的预期变化(event, pre-event, post-event, and state, change of state, and expected change of state)
期限,(The term,)事件(event),通常表示对象状态已发生或将要发生变化.该术语还用于指代在对象或应用程序中发生的某些活动,例如,来自最终用户的手势处理(例如,单击按钮)或长时间运行任务期间的进度报告等活动.(, typically means that a change in state of an object has occurred or is about to occur. The term is also used in reference to some activity taking place within an object or application — activity like the processing of a gesture from an end user (e.g., button clicked), or the reporting of progress during a long-running task.) 术语"状态"是指对象或应用程序中一个或多个变量的当前值集.状态改变意味着对象中一个或多个变量的值已改变.在事件通知过程中,状态变化或预期状态变化是引发事件的主要动机.因此,我们有两种方法来定义相对于状态更改的事件:紧接在状态更改之前或紧随状态更改之后.前者被称为事前事件,后者被称为事后事件.(The term, “state,” refers to the current set of values of one or more variables in an object or application. A change in state means the value of one or more variables within an object has changed. In the event notification process, changes in state, or expected changes in state, are primary motivations for raising events. So, we have two ways to define an event relative to a change in state: immediately prior to a change in state, or immediately after a change in state. While the former are referred to as pre-events, the latter are referred to as post-events.) 事件后宣布状态已经发生改变,事件前宣布状态改变即将发生的事实.事前事件可以实现为可取消的,这意味着订户可以在状态改变发生之前取消事件,从而防止状态改变的发生,或者防止长时间运行的任务的进一步处理.(Post-events announce that the change in state has already occurred, and pre-events announce the fact that a change in state is about to occur. Pre-events can be implemented as cancellable — meaning that the subscriber may cancel the event before the change in state occurs, thereby preventing the change in state from occurring, or preventing the further processing of a long-running task.)
事件发布者,事件源,主题(event publisher, event source, subject)
这些是其他类或对象感兴趣的类或对象.事件发布者保持其内部状态,并通过引发事件或类似的通知机制来通知其他类(订阅者).(These are the classes or objects of which their state is of interest to other classes or objects. Event publishers maintain their internal state, and notify other classes (subscribers) through the raising of events or similar notification mechanisms.)
事件订阅者,接收器,侦听器,观察者(event subscriber, sink, listener, observer)
这些是对事件发布者的状态更改(或状态的预期更改)感兴趣的类或对象.这些术语指的是通常响应事件发生而执行某些操作的类或对象.(These are the classes or objects that are interested in changes in state (or expected changes in state) of the event publishers. These terms refer to the classes or objects that typically perform some action in response to the occurrence of an event.)
引发,引发或触发事件;通知或事件通知(raise, fire, or trigger an event; notification, or event notification)
事件通知(通常表示为"引发事件"或"引发事件"或"触发事件")通常以事件发布者的形式在一个或多个订户中调用方法.因此,引发事件最终意味着事件发布者中的代码导致一个或多个订户中的代码运行.(Event notifications (frequently expressed as, “fire an event” or “raise an event” or “trigger an event”) are generally in the form of the event publisher calling a method in one or more subscribers. Consequently, the raising of an event ultimately means that code in the event publisher causes code in one or more subscribers to run.) 如果没有[事件]的订阅者在发布者处注册,则不会引发该事件.(In cases where no subscribers [to an event] have registered with the publisher, the event would not be raised.) 请注意,本文将事件描述为"引发"(不是"触发"或"触发").该约定来自编写大部分.NET Framework的开发人员团队(Cwalina和Abrams,2006年).他们更喜欢"提高"一词,因为它没有表达"开火"或"触发"的消极含义.(Please note that in this article, events are described as “raised” (not “fired” or “triggered”). This convention comes from the team of developers who authored much of the .NET Framework (Cwalina and Abrams, 2006). They prefer the term, “raise,” because it doesn’t have the negative connotations of the expressions, “fire” or “trigger.")
事件数据,与事件相关的数据和事件参数(“事件args”)(event data, event-related data, and event arguments (“event args”))
引发事件时,发布者将经常包含通过事件通知过程发送给订阅者的数据.该数据可能与引发的特定事件有关,并且事件订阅者会感兴趣.(When an event is raised, the publisher will frequently include data that gets sent to the subscribers through the event notification process. This data is presumably relevant to the particular event that was raised, and would be of interest to the event subscribers.) 例如,重命名文件时可以引发事件.与该特定"文件重命名"事件相关的数据可以包括(1)名称更改之前的文件名称,以及(2)名称更改之后的文件名称.这些文件名可以包括在引发"文件重命名"事件期间发送给订户的事件数据.(For example, an event can be raised when a file gets renamed. Data relevant to that particular “file renamed” event could include (1) the name of the file before the name was changed, and (2) the name of the file after the name was changed. Those file names could comprise the event data that are sent to the subscribers during the raising of the “file renamed” event.)
代表类型,代表(Delegate Type, Delegates)
清楚了解.NET委托类型对于了解.NET Framework中实现的事件至关重要.因此,本文的大部分内容都致力于解释委托与事件之间的关系.(A clear understanding of the .NET Delegates type is crucial to the understanding of events as implemented in the .NET Framework. Consequently, much of this article is dedicated to explaining the relationship between delegates and events.)
“事件处理程序"的两种含义(Two Meanings of “Event Handler”)
本文以外的文献经常使用"事件处理程序"一词来指代(1)在其上定义事件的委托人(在发布者中),或(2)在该事件中注册的任何方法(在订户).此外,Visual Studio中的Intellisense将事件处理方法(在订户中)简称为"处理程序”.为了清楚起见,本文使用表示委托的表达式"事件处理程序”,而使用表示使用事件注册的任何方法的表达式"事件处理方法".(The literature outside of this article frequently uses the term, “event handler,” in reference to either (1) the delegate upon which an event is defined (in the publisher), or (2) any method registered with the event (in the subscriber). Furthermore, Intellisense in Visual Studio refers to an event handling method (in the subscriber) as simply, “handler.” For purposes of clarity, this article uses the expression, “event handler,” in reference to the delegate, while using the expression,“event handling method,” in reference to any method registered with an event.) 总结"事件处理程序"是事件所基于的委托,而"事件处理方法"是在引发事件时在订户中调用的方法.(To summarize; an “event handler” is the delegate upon which an event is based, while an “event handling method” is a method called in the subscriber when an event is raised.) 事件处理程序是委托,尽管委托不一定是事件处理程序(除了支持事件,委托还有很多用途).在本文后面会更详细地介绍代表,但仅在与事件相关的程度上.(Event handlers are delegates, although delegates are not necessarily event handlers (there are many uses of delegates beyond supporting events). Delegates are presented in more detail later in this article, but only to the extent that they are relevant to events.)
.NET事件和GoF Observer模式(.NET events and the GoF Observer pattern)
在.NET Framework中实现并在本文中描述的事件构成了观察者模式的.NET优化实现,该模式由"四人帮"或" GoF"记录(Gamma等,1995).用于实现事件(尤其是代理)的.NET机制大大减少了在.NET应用程序中实现观察者模式所需的工作量.(Events, as implemented in the .NET Framework and as described in this article, constitute a .NET optimized implementation of the Observer Pattern that was documented by the “Gang of Four” or “GoF” (Gamma et al.1995). The .NET mechanisms used to implement events (delegates in particular) substantially reduce the amount of work required to implement the Observer pattern in .NET applications.)
3.代表们(3. Delegates)
为了了解在.NET应用程序中实现的事件,必须对.NET有清晰的了解.(In order to understand events, as implemented in .NET applications, one must have a clear understanding of the .NET) delegate
类型及其在事件实现中扮演的角色.(type and the role it plays in the implementation of events.)
3.1代表的定义和使用(3.1 Definition and Usage of Delegates)
委托可以理解为持有对方法的引用的智能容器,而不是持有对对象的引用的容器.委托可以包含对零个,一个或多个方法的引用.为了使特定的委托实例调用方法,必须在委托实例中注册该方法.注册后,该方法将添加到委托的方法引用的内部集合(委托的"调用列表").委托可以在委托实例可见的任何类中保存对静态方法或实例方法的引用.委托实例可以同步或异步调用其引用的方法.当异步调用时,这些方法在单独的线程池线程上执行.当一个委托实例被调用(“被调用”)时,委托所引用的所有方法都会被委托自动调用.(Delegates can be understood as intelligent containers that hold references to methods, as opposed to containers that hold references to objects. Delegates can contain references to zero, one, or many methods. In order for a method to be called by a particular delegate instance, that method must be registered with the delegate instance. When registered, the method is added to the delegate’s internal collection of method references (the delegate’s “invocation list”). Delegates can hold references to static methods or instance methods in any class visible to the delegate instance. Delegate instances can call their referenced methods either synchronously, or asynchronously. When called asynchronously, the methods execute on a separate thread pool thread. When a delegate instance is invoked (“called”), then all methods referenced by the delegate are called automatically by the delegate.) 委托不能仅包含对任何方法的引用.代表可以持有参考(Delegates cannot contain references to just any method. Delegates can hold references)只要(only)使用方法签名定义的方法(to methods defined with a method signature that)究竟(exactly)匹配委托人的签名.(matches the signature of the delegate.) 考虑以下委托声明:(Consider the following delegate declaration:)
public delegate void MyDelegate(string myString);
请注意,委托声明看起来像方法声明,但没有方法主体.(Notice that the delegate declaration looks like a method declaration, but with no method body.)
委托的签名确定委托可以引用的方法的签名.因此,上面的示例委托(The signature of the delegate determines the signature of methods that can be referenced by the delegate. So, the sample delegate above () MyDelegate
)只能保存对返回的方法的引用() can hold references only to methods that return) void
同时接受一个字符串参数.因此,可以向以下实例注册以下方法(while accepting a single string argument. Consequently, the following method can be registered with an instance of) MyDelegate
:(:)
private void MyMethod(string someString)
{
// method body here.
}
但是,以下方法不能被(The following methods, however, cannot be referenced by a) MyDelegate
实例,因为它们的签名与(instance because their signatures do not match that of) MyDelegate
.(.)
private string MyOtherMethod(string someString)
{
// method body here.
}
private void YetAnotherMethod(string someString, int someInt)
{
// method body here.
}
在声明新的委托类型之后,必须创建该委托的实例,以便可以向该委托实例注册方法,并最终由该委托实例调用这些方法.(After a new delegate type is declared, an instance of that delegate must be created so that methods can be registered with, and ultimately invoked by, the delegate instance.)
// instantiate the delegate and register a method with the new instance.
MyDelegate del = new MyDelegate(MyMethod);
实例化委托后,可以向委托实例注册其他方法,如下所示:(After a delegate is instantiated, additional methods can be registered with the delegate instance, like this:)
del += new MyDelegate(MyOtherMethod);
此时,可以像下面这样调用委托:(At this point, the delegate can be invoked, like this:)
del("my string value");
而且,因为两者(And, because both) MyMethod
和(and) MyOtherMethod
已在(are registered with the) MyDelegate
实例(命名(instance (named) del
),该实例将同时调用(), that instance will invoke both) MyMethod
和(and) MyOtherMethod
当执行上述行时,传递每个字符串值"我的字符串值".(when the above line executes, passing each the string value, “my string value.")
委托和重载方法(Delegates and Overloaded Methods)
在重载方法的情况下,只有具有与委托人签名完全匹配的签名的特定重载才能由委托人引用(或注册).当您编写将重载方法注册到委托实例的代码时,C#编译器将自动选择并注册具有匹配签名的特定重载.(In the case of an overloaded method, only the particular overload having a signature that exactly matches the signature of the delegate can be referenced by (or registered with) the delegate. When you write code that registers an overloaded method with a delegate instance, the C# compiler will automatically select and register the particular overload with a matching signature.) 因此,例如,如果您的应用程序声明了以下委托类型…(So, for example, if your application declared the following delegate type…)
public delegate int MyOtherDelegate(); // returns int, no parameters
…并且您注册了一个名为的重载方法(… and you registered an overloaded method named) MyOverloadedMethod
与一个实例(with an instance of) MyOtherDelegate
, 像这样…(, like this…)
anotherDel += new MyOtherDelegate(MyOverloadedMethod);
… C#编译器将仅使用匹配的签名注册特定的重载.在以下两个重载中,只有第一个会在(… the C# compiler will register only the particular overload with a matching signature. Of the following two overloads, only the first would be registered with the) anotherDel
的实例(instance of the) MyOtherDelegate
类型:(type:)
// requires no parameters - so can be registered with a MyOtherDelegate
// instance.
private int MyOverloadedMethod()
{
// method body here.
}
// requires a string parameter - so cannot be registered with a MyOtherDelegate instance.
private int MyOverloadedMethod(string someString)
{
// method body here.
}
单个委托不能选择性地注册或调用两个(多个)重载.如果需要同时调用两个(多个)重载,则需要其他委托类型-每个签名一个委托类型.然后,您的特定于应用程序的逻辑将确定要调用的委托,并因此(由具有相应签名的委托)调用哪个重载.(A single delegate cannot selectively register or call both (multiple) overloads. If you need to call both (multiple) overloads, then you would need additional delegate types — one delegate type per signature. Your application-specific logic would then determine which delegate to invoke, and therefore which overload is called (by the delegate with the corresponding signature).)
3.2为什么要派代表?(3.2 Why Delegates?)
如果这是您对代理的首次介绍,您可能会想:“为什么要打扰?直接调用该方法更简单-那么通过代理有什么好处?"(If this is your first introduction to delegates, you may be wondering, “Why bother? It’s just simpler to call the method directly — so what is the benefit of going through a delegate?")
必要的间接(Necessary Indirection)
一个简短的答案(对于上面的"为什么要打扰?“问题)是,我们编写的代码或所使用的组件无法始终"知道"在特定时间点调用哪种特定方法.因此,委托的一个重要观点是,它们为.NET组件提供了一种调用代码的方法-.NET组件无需了解方法签名(委托类型要求)以外的代码知识.例如,.NET Framework组件(如Timer组件)经常需要执行您编写的代码.因为Timer组件可能无法知道要调用哪个特定方法,所以它指定了要调用的委托类型(并因此是方法的签名).然后,通过使用Timer组件期望的委托类型的委托实例注册方法,将具有必需签名的方法连接到Timer组件.然后,Timer组件可以通过调用委托来运行您的代码,该委托又调用您的方法.请注意,Timer组件仍然对您的特定方法一无所知. Timer组件仅知道的是委托.反过来,委托人知道您的方法,因为您向该委托人注册了方法.最终结果是Timer组件导致您的方法运行,但对您的特定方法一无所知.(A brief answer (to the “why bother?” question above) is that the code we write or components we use cannot always “know” which specific method to call at a particular point in time. So, one important perspective of delegates is that they provide a way for .NET components to call your code — without the .NET component having to know anything about your code beyond the method signature (as mandated by the delegate type). For example, .NET Framework components, like the Timer component, frequently need to execute code that you write. Because the Timer component cannot possibly know which specific method to call, it specifies a delegate type (and therefore signature of a method) to be invoked. Then you connect your method — with the requisite signature — to the Timer component by registering your method with a delegate instance of the delegate type expected by the Timer component. The Timer component can then run your code by invoking the delegate which, in turn, calls your method. Note that the Timer component still knows nothing about your specific method. All the Timer component knows about is the delegate. The delegate, in turn, knows about your method because you registered your method with that delegate. The end result is that the Timer component causes your method to run, but without knowing anything about your specific method.) 就像上面的Timer组件示例一样,我们可以以某种方式使用委托,使我们能够编写代码,而我们的代码不必"知道"最终将在特定点调用的特定方法.我们的代码可以调用委托实例,而不是在此时调用方法,该委托实例又调用在委托实例中注册的所有方法.最终结果是,即使要调用的特定方法没有直接写到我们的代码中,也将调用兼容方法.(Just like the Timer component example above, we can make use of delegates in a way that enables us to write our code without our code having to “know” the specific method that will ultimately be called at a specific point. Rather than calling a method at that point, our code can invoke a delegate instance — which, in turn, calls any methods that are registered with the delegate instance. The end result is that a compatible method is called even though the specific method to be called was not written directly into our code.)
同步和异步方法调用(Synchronous and Asynchronous Method Invocation)
所有委托都固有地提供了同步和异步方法调用.因此,通过委托实例调用方法的另一个常见原因是异步调用方法-在这种情况下,被调用方法在单独的线程池线程上运行.(All delegates inherently provide for both synchronous and asynchronous method invocation. So, another common reason to call methods via delegate instances is to invoke methods asynchronously — in which case the called method runs on a separate thread pool thread.)
活动基金会(Event Foundation)
正如您将在本文后面看到的那样,委托在.NET Framework中的事件实现中扮演着不可或缺的角色.简而言之,委托人在事件发布者及其订阅者之间提供了必要的间接层.为了在发布者和订阅者之间保持清晰的隔离,此间接操作是必需的,这意味着可以添加和删除订阅者,而无需以任何方式修改发布者.在事件发布的情况下,使用委托可以使事件发布者对任何订阅者一无所知,同时仍向任何/所有订阅者广播事件和相关的事件数据.(As you’ll see later in this article, delegates play an integral role in the implementation of events in the .NET Framework. In brief, delegates provide a necessary layer of indirection between event publishers and their subscribers. This indirection is necessary in order to maintain a clean separation between the publisher and subscriber(s) — meaning that subscribers can be added and removed without the publisher needing to be modified in any way. In the case of event publication, the use of a delegate makes it possible for an event publisher to know nothing about any of its subscribers while still broadcasting events and associated event data to any/all subscribers.)
其他用途(Other Uses)
代理在.NET应用程序中除了已列出的角色以外,还发挥着重要作用.这些其他角色在这里不再赘述,因为本文的目的只是集中于委托在.NET应用程序中实现事件时所起的基本作用.(Delegates serve important roles in .NET applications beyond those already listed. Those other roles will not be further presented here because the intent of this article is to focus only on the foundational role that delegates serve in the implementation of events in .NET applications.)
3.3委托内部(3.3 Delegate Internals)
声明一个委托会导致创建一个新类(Declaring a Delegate Results in A New Class Being Created)
您编写的委托声明足以定义整个和新的委托类. C#编译器接受您的委托声明,并在输出程序集中插入一个新的委托类.新类的名称是您在委托声明中提供的委托类型的名称.您在委托声明中指定的签名将成为新类中用于调用任何/所有委托的引用方法的方法的签名(特别是(A delegate declaration that you write is sufficient to define an entire and new delegate class. The C# compiler takes your delegate declaration and inserts a new delegate class in the output assembly. The name of that new class is the name of the delegate type you supply in your delegate declaration. The signature you specify in your delegate declaration becomes the signature of the methods in the new class used to call any/all of the delegate’s referenced methods (specifically the) Invoke
和(and) BeginInvoke
方法).此新类扩展(继承)(methods). This new class extends (inherits)) System.MulticastDelegate
.因此,新委托类中可用的大多数方法和属性都来自(. So most of the methods and properties available in your new delegate class come from) System.MulticastDelegate
.的(. The) Invoke
,(,) BeginInvoke
和(, and) EndInvoke
方法由C#编译器在输出程序集中创建新类时插入(这些方法可以调用,以使委托调用任何/所有引用的方法-(methods are inserted by the C# compiler when it creates the new class in the output assembly (these are the methods you can call to cause the delegate to invoke any/all referenced methods —) Invoke
用于同步调用,以及(for synchronous invocation, and) BeginInvoke
和(and) EndInvoke
用于异步调用).(used in asynchronous invocations).)
从您的委托声明创建的新类可以理解为已完成且功能齐全(The new class created from your delegate declaration can be understood as being a completed and full-blown) MulticastDelegate
具有您在委托声明中提供的类型名称的实现,并能够使用在委托声明中也提供的特定签名来调用方法.(implementation that has the type name you supplied in your delegate declaration, and is capable of calling methods with the specific signature that you also supplied in your delegate declaration.)
例如,当C#编译器遇到以下委托声明时…(As an example, when the C# compiler encounters the following delegate declaration…)
public delegate string MyFabulousDelegate(int myIntParm);
…编译器将插入一个名为(… the compiler inserts a new class named) MyFabulousDelegate
进入输出组件.的(into the output assembly. The) Invoke
,(,) BeginInvoke
和(, and) EndInvoke
的方法(methods of the) MyFabulousDelegate
类包括(class include the) int
参数并返回(parameter and returned) string
各自方法签名中的值.(value in their respective method signatures.)
应该注意的是(It should be noted that) MulticastDelegate
是一个特殊的类,因为编译器可以从中派生,但是您不能显式地派生它.您对C#的使用(is a special class in that compilers can derive from it, but you cannot derive from it explicitly. Your use of the C#) delegate
关键字和相关的语法是如何指示C#编译器扩展(keyword and associated syntax is how you instruct the C# compiler to extend) MulticastDelegate
为了您的目的.(for your purposes.)
组播的含义(Meaning of Multicast)
“多播"的含义(The meaning of “multicast” in) System.MulticastDelegate
是委托能够保存对多个方法的引用,而不仅仅是一个方法.对于持有对多个方法的引用的委托实例,在调用委托实例时将调用所有引用的方法.(is that the delegate is capable of holding references to multiple methods — not just one method. In the case of delegate instances that hold references to multiple methods, all referenced methods are called when the delegate instance is invoked.)
代表是一成不变的(Delegates are Immutable)
委托实例是不可变的,这意味着一旦创建了委托实例,就无法对其进行修改.因此,当您向委托注册方法时,实际上是创建了一个新的委托实例,该实例在其调用列表中包括其他方法.如果从委托实例取消注册方法,则将返回新的委托实例,该实例的调用列表中将省略未注册的方法.如果要创建特定委托类型的新对象变量,然后将其设置为等于现有(特定类型)委托实例,则将获得委托的完整副本.对副本的修改(例如,注册其他方法)将仅影响副本.原始实例的调用列表将保持不变.(Delegate instances are immutable — meaning that once a delegate instance is created, it cannot be modified. So, when you register a method with a delegate, what is happening is that a new delegate instance is created that includes the additional method in its invocation list. If you unregister a method from a delegate instance, a new delegate instance is returned that has the unregistered method omitted from its invocation list. If you were to create a new object variable of a particular delegate type, then set it equal to an existing delegate instance (of that particular type), you would get a complete and separate copy of the delegate. Modifications to the copy (e.g., registering an additional method) would affect only the copy. The invocation list of the original instance would remain unchanged.)
委托不是功能指针(Delegates are not Function Pointers)
最后,C和C ++程序员将认识到委托类似于C样式的函数指针.但是,重要的区别在于,委托不仅仅是指向原始内存地址的指针.而是,委托实例是由.NET CLR管理的类型安全对象,它们专门引用一个或多个"方法”(与内存地址相对).(Finally, C and C++ programmers will recognize that delegates are similar to C-style function pointers. An important difference, though, is that a delegate is not simply a pointer to a raw memory address. Instead, delegate instances are type-safe objects that are managed by the .NET CLR and that specifically reference one or more “methods” (as opposed to memory addresses).)
3.4代表都是一样的(没有根本不同类型的代表)(3.4 Delegates Are All The Same (there are no fundamentally differing types of delegates))
这些陈述都是正确的:(These statements are all true:)
“(")如果您已经看到一位代表,那么您已经看到了他们全部.(If you’ve seen one delegate, you’ve seen them all.)"(")
要么(or)
“(")创建的所有代表相等.(All delegates are created equal.)"(")
要么(or)
“(")委托就是委托,就是委托.(A delegate is a delegate is a delegate.)"(")
当您了解不同的"类型"的代表时,您应该理解,在内部,所有代表都是相同的.对于.NET Framework提供的委托以及您为自己目的创建的委托,都是如此.说"他们都是一样的"特别意味着所有代表(1)都从(When you read about different “types” of delegates, you should understand that, internally, all delegates are the same. This is true for delegates provided by the .NET Framework and for delegates you create for your own purposes. To say “they are all the same” specifically means that all delegates (1) inherit from) System.MulticastDelegate
,而后者又继承自(, which in turn inherits from) System.Delegate
;和(2)提供相同的成员集,包括(; and (2) provide the same set of members, including the) Invoke
,(,) BeginInvoke
和(, and) EndInvoke()
方法等(methods, etc.)
代表类型的区别仅在于:(What differentiates delegate types is nothing more than:)
-
的(The)类型名称(type name)代表.(of the delegate.)
-
的(The)签名(signature)委托的名称-包括返回类型,数量和参数类型.(of the delegate — including return type and number and types of parameters.)
-
预期用途或角色(Intended usage or role)代表.(of the delegate.) 以通用谓词委托((Take, for example, the generic Predicate delegate ()
System.Predicate<T>
).这就是使它成为"谓词委托"的原因:(). Here is what makes it a “Predicate delegate”:) -
的(The)类型名称(type name):(:)
Predicate
-
的(The)签名(signature):返回(: returns)
bool
,接受一个(, accepts a single)object
可以在设计时指定通用类型的类型化参数.(typed parameter for which the type, being generic, can be specified at design time.) -
预期用途或角色(Intended usage or role):此委托将引用定义一组条件并确定指定对象是否满足这些条件的方法.(: this delegate will reference a method that defines a set of criteria and determines whether the specified object meets those criteria.) 除了类型名称,签名和预期用途之外,(Beyond the type name, signature, and intended usage, the)
Predicate<T>
代表具有与其他任何代表相同的成员集,包括(delegate has the same set of members that any other delegate has, including)Invoke
,(,)BeginInvoke
因此,这就是语句"代表都一样"的含义.(, etc. Consequently, this is what is meant by the statement, “delegates are all the same.") 要明确的是(To be clear, it is)不(not)在这种情况下(the case that the)Predicate<T>
委托具有任何其他方法或属性,可以帮助其履行其预期的职责.如果某些委托人具有其他委托人不具备的属性或方法,那么这些委托人将具有不同或独特的功能,因此我们无法说出它们都是一样的.(delegate has any additional methods or properties that help it to fulfill its intended role. If some delegates had properties or methods that other delegates do not have, then those delegates would have different or unique capabilities and we therefore would not be able to say they are all the same.) 关于预期的使用角度;您可以随意使用任何代表(Regarding the intended usage perspective; you are free to use any delegate for purposes)不(not)代表创建者的意图-代表不受任何特定用法的约束.例如,您可以使用(intended by the delegate’s creators — as delegates are not tied to any particular usage. You could, for example, use a)Predicate<T>
委托以调用返回的任何方法(delegate to call any method that returns)bool
并接受一个(and accepts a single)object
类型化参数-即使这些方法不能确定指定对象是否满足任何条件(即(typed parameter — even if those methods do not determine whether the specified object meets any criteria (which is the intended usage of the)Predicate<T>
代表).当然,(delegate). Granted,)您不应将委托用于其预定服务以外的目的(you should not use delegates for purposes other than those which they are intended to serve),.NET Framework中提供预建委托的大部分价值(例如(, as much of the value in the .NET Framework providing pre-built delegates (like)Predicate<T>
),这样我们就可以了解他们的角色,而无需深入研究一堆代码来了解他们的实际工作.() is that we can understand the role they play without having to dig through a bunch of code to find out what they are actually doing.) 委托类型的名称传达其在代码中的预期作用.因此,请确保使用适当的委托人类型,或者使用具有参考意义的类型名创建自己的委托人,即使可以使用具有必要签名的另一个委托人(但根据您的特定用法可能会产生误导性的名称)也可以使用.(The name of a delegate type communicates its intended role in your code. So be sure to use the appropriate delegate type, or create your own with an informative type name, even if another delegate with the requisite signature — but a potentially misleading name given your particular usage — is available.)
4.代表与活动的关系(4. The Relationship Between Delegates and Events)
.NET编程中的事件基于委托.具体而言,事件可以理解为围绕特定委托人提供概念性包装.然后,该事件控制对该基础委托的访问.当客户端订阅事件时,该事件最终将订阅方法注册到基础委托.然后,在引发事件时,基础委托会调用向其注册的每个方法(委托).然后,在事件的上下文中,委托人充当引发事件的代码与作为响应执行的代码之间的中介,从而使事件发布者与订阅者脱钩.(Events in .NET programming are based on delegates. Specifically, an event can be understood as providing a conceptual wrapper around a particular delegate. The event then controls access to that underlying delegate. When a client subscribes to an event, the event ultimately registers the subscribing method with the underlying delegate. Then, when the event is raised, the underlying delegate invokes each method that is registered with it (the delegate). In the context of events, then, delegates act as intermediaries between the code that raises events and the code that executes in response — thereby decoupling event publishers from their subscribers.) 事件本身并不维护订户列表.相反,事件控制对某些基础订阅者列表的访问-并且该列表通常作为委托实现(尽管可以使用其他列表类型的对象或集合来代替委托).(Events do not, by themselves, maintain a list of subscribers. Instead, events control access to some underlying list of subscribers — and that list is typically implemented as a delegate (although other list-type objects or collections can serve in place of a delegate).)
4.1事件处理程序(一般)(4.1 Event Handlers (in general))
支持事件存在的委托称为"事件处理程序”.需要明确的是,“事件处理程序"是一个委托,尽管委托通常不是事件处理程序.(A delegate that exists in support of an event is referred to as an “event handler”. To be clear, an “event handler” is a delegate, although delegates are frequently not event handlers.) 不幸的是,许多写有关事件的作者都使用"事件处理程序"一词来指代(1)事件所基于的委托人和(2)引发事件时委托人调用的方法.为了避免这种情况造成混乱,本文仅在引用委托时使用表达式"事件处理程序”,而在引用到委托中注册的任何方法时使用表达式"事件处理方法” .(Unfortunately, many authors writing about events use the term, “event handler”, in reference to both (1) the delegate upon which an event is based, and (2) a method called by the delegate when the event is raised. In order to avoid confusion resulting from this state of affairs, this article uses the expression, “event handler,” only in reference to the delegate, while using the expression, “event handling method,” in reference to any method registered with the delegate.)
自定义事件处理程序(Custom Event Handlers)
您可以定义自己的事件处理程序(代理),也可以使用.NET Framework提供的事件处理程序之一(即,(You can define your own event handlers (delegates), or you can use one of the event handlers provided by the .NET Framework (i.e.,) System.EventHandler
或通用(, or the generic) System.EventHandler<TEventArgs>
).下面的示例事件声明使用了自定义事件处理程序,而不是使用Framework提供的事件处理程序.(). The following sample event declaration make use of a custom event handler rather than using a Framework-provided event handler.)
考虑以下:(Consider the following:)
Line 1: public delegate void MyDelegate(string whatHappened);
Line 2: public event MyDelegate MyEvent;
第1行声明了可以为其分配任何方法的委托类型-只要该方法返回(Line 1 declares a delegate type for which any method can be assigned — provided that the method returns) void
并接受一个(and accepts a single) string
论据:(argument:)
-
public
—范围,指定我们类之外的对象可以引用委托.如果在事件发布类中声明了委托类型,则将需要在公共范围内定义委托类型,以便事件订阅者可以看到它并声明用于注册其事件处理方法的实例(稍后将对此进行详细介绍).(— scope specifying that objects outside of our class can reference the delegate. If the delegate type is declared within the event publishing class, then it will need to be publicly scoped so that event subscribers can see it and declare instances of it with which to register their event handling methods (more on this later).) -
delegate
—关键字,用于在.NET Framework中声明自定义委托.(— keyword used to declare custom delegates in the .NET Framework.) -
void
—返回类型.这是委托签名的一部分,因此,注册方法必须指定返回类型.(— return type. This is part of the delegate signature, and therefore the return type that registering methods must specify.) -
MyDelegate
—键入代表的名称.(— type name of the delegate.) -
(string whatHappened)
-签名的其余部分.向该事件注册的任何方法都必须接受一个(— the rest of the signature. Any method that registers with the event must accept a single)string
参数(除了返回(argument (in addition to returning)void
).().) 第2行根据委托类型声明了一个事件.请注意,该事件(名为(Line 2 declares an event in terms of the delegate type. Notice that the event (which is named)MyEvent
)非常类似于方法声明,但是其数据类型指定为委托类型:() is declared very much like a method declaration — but with its data type specified as the delegate type:) -
public
—范围,指定我们类之外的对象可以预订事件.(— scope specifying that objects outside of our class can subscribe to the event.) -
event
—用于定义事件的关键字.(— keyword used to define the event.) -
MyDelegate
—事件的数据类型(这是第1行中定义的自定义委托类型.)(— data type of the event (this is the custom delegate type defined in Line 1.)) -
MyEvent
—事件的名称.(— name of the event.) 在第1行中声明的委托只是一个普通委托(与所有委托一样),并且可以用于委托可以实现的任何目的.第2行(即委托类型的用法)将委托转换为事件处理程序.为了传达特定的委托人类型被用作事件处理程序,出现了一种命名约定,其中委托人类型名称以” Handler"结尾(稍后会有更多介绍).(The delegate declared in Line 1 is just an ordinary delegate (as are all delegates), and can be used for any purpose delegates can fulfill. Line 2 (i.e., the usage of the delegate type) is what turns that delegate into an event handler. In order to communicate that a particular delegate type is being used as an event handler, a naming convention has emerged whereby the delegate type name ends with “Handler” (more on this later).)
标准化事件处理程序(Standardized Event Handlers)
虽然您可以创建自己的事件处理程序(有时可能需要),但您应使用以下一种(While you can create your own event handlers (and sometimes you might need to), you should use one of the) EventHandler
.NET Framework提供的委托,其中框架的事件处理程序之一可以与您的特定事件实现一起使用.许多事件利用可以具有相同或相同签名的事件处理程序.因此,您可以/应该使用内置的事件处理程序,而不用使许多仅因类型名称而异的委托使源代码混乱,因为这样做可以减少编写和维护所需的代码量,并使您的代码更容易理解.如果有人在阅读您的代码后发现您正在根据(delegates provided by the .NET Framework in cases where one of the Framework’s event handlers would work with your particular event implementation. Many events make use of event handlers that can have common or identical signatures. So, rather than clutter your source code with many delegates that differ only by type name, you can/should make use of the built-in event handlers, as doing so reduces the amount of code you would need to write and maintain, and makes your code more easily understood. If someone reading your code sees you are basing an event on the) System.EventHandler
例如,委托,那么他们会自动了解您的事件实现,而无需进一步了解.(delegate, for example, then they automatically know a lot about your event implementation without having to look further.)
4.2非通用System.EventHandler委托(4.2 The Non Generic System.EventHandler Delegate)
.NET Framework的1.x版(非通用)中提供(Available in version 1.x of the .NET Framework, the non generic) System.EventHandler
委托强制执行事件处理程序的约定(下面将详细介绍),该事件处理程序在接受两个参数时不返回任何值,第一个是(delegate enforces the convention (described in greater detail below) of event handlers returning no value while accepting two parameters, the first being an) object
型参数(用于保存引发事件的类的引用)和第二个类型的参数(-typed parameter (to hold a reference to the class raising the event) and a second parameter of type) System.EventArgs
或其子类(用于保存任何事件数据).(or a subclass thereof (to hold any event data).) System.EventArgs
稍后介绍.(is presented later.)
.NET Framework就是这样声明(This is how the .NET Framework declares the) System.EventHandler
代表.(delegate.)
public delegate void EventHandler(object sender, EventArgs e);
4.3通用System.EventHandler 委托(4.3 The Generic System.EventHandler Delegate)
从.NET Framework 2.0版开始可用,通用(Available since the 2.0 version of the .NET Framework, the generic) System.EventHandler
委托执行与非通用版本相同的签名约定-但第二个接受通用类型参数,(delegate enforces the same signature convention as enforced by the non generic version — but accepts a generic type parameter for the second,) System.EventArgs
,参数.(, parameter.)
此内置委托的声明强制执行以下约束:(The declaration of this built-in delegate enforces the constraint that the type,) TEventArgs
,属于(, be of type) System.EventArgs
(当然包括其子类):((including, of course, subclasses thereof):)
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e)
where TEventArgs : EventArgs;
现在,假设您要严格键入发件人,而不是键入(Now suppose you want to strongly type the sender, rather than having it typed as) object
.您可以利用泛型来创建自己的泛型事件处理程序:(. You can leverage generics to create your own generic event handler:)
public delegate void MyGenericEventHandler<T, U>(T sender,
U u) where U : EventArgs;
然后,您可以使用此自定义通用事件处理程序来另外指定类型安全的(You can then use this custom generic event handler to additionally specify a type-safe) sender
参数(即因此限制了引发事件时可以传达的对象的类型):(parameter (i.e., thereby limiting the type of object that can be communicated as having raised the event):)
public event MyGenericEventHandler<MyPublisher, MyEventArgs> MyEvent;
目的是,此事件仅由类型为的对象引发(The intent here would be that this event will only be raised by objects of type) MyPublisher
.因此,该事件的订阅者将只能订阅由(. Subscribers to the event would therefore be able to subscribe only to events published by the) MyPublisher
类.(class.)
5.事件参数(EventArgs)(5. Event Arguments (EventArgs))
事件参数(有时称为"事件args”)构成事件的发布者在事件引发期间发送给订阅者的数据.大概此数据与事件的发生有关.例如,当引发"刚刚删除文件"事件时,事件参数可能会包括名称更改之前的文件名称以及名称更改之后的文件名称.事件处理方法可以读取事件参数(称为"事件数据”)以了解有关事件发生的更多信息.(Event arguments — sometimes referred to as “event args” — constitute the data sent by the publisher of an event to the subscribers during the raising of the event. Presumably this data is relevant to the occurrence of the event. For example, when a “file was just deleted” event is raised, the event arguments would likely include the name of the file before the name change, as well as the name of the file after the name was changed. The event handling methods can read the event arguments (referred to as “event data”) to learn more about the event occurrence.)
5.1 System.EventArgs的作用(5.1 The Role of System.EventArgs)
您可以通过两种基本选择在事件中包含事件参数.(You have two basic alternatives for including event arguments with your events.)
- 您可以将所有事件参数封装为派生自一个类的属性(You can encapsulate all event arguments as properties of a class that derives from)
System.EventArgs
.在运行时,引发事件时,该类的实例将被发送到事件订阅者.事件订阅者将事件参数作为该类的属性读取.(. At runtime, an instance of that class is then sent to the event subscribers when the event is raised. The event subscribers read the event arguments as properties of that class.) - 您可以避免使用(You can avoid the use of)
System.EventArgs
而是声明单个事件参数-就像在方法声明中包括参数一样.(and, instead, declare individual event arguments — much as you would include arguments in a method declaration.)不鼓励这种方法(This approach is discouraged)出于第5.2节中所述的原因.(for reasons described in section 5.2.) 强烈建议您使用上面列出的第一种方法,并且通过.NET Framework将对它的支持(The first alternative listed above is strongly encouraged, and support for it is built into the .NET Framework through the)System.EventArgs
类.按照约定,在.NET Framework组件中实现的事件将其事件参数作为实例提供(class. Events implemented in .NET Framework components, by convention, provide their event arguments as instances)System.EventArgs
,或作为特定于事件的子类(, or as event-specific subclasses of)System.EventArgs
.(.) 有些事件没有数据.在这些情况下,(Some events carry no data. In these cases,)System.EventArgs
用作占位符,主要用于在所有事件之间保持一个一致的事件处理程序签名,而不管事件携带数据还是不携带数据.如果事件没有数据,则事件发布者将发送该值,(is used as a placeholder, primarily for purposes of keeping one consistent event handler signature across all events regardless of whether the events carry data or carry no data. In cases of events with no data, the event publisher sends the value,)System.EventArgs.Empty
,在活动开始期间.(, during the raising of the event.)
5.2扩展System.EventArgs(5.2 Extending System.EventArgs)
存在的(The existence of) System.EventArgs
及其建议的用途是为了支持事件实现约定.事件的发布者当然可以在不使用事件的情况下指定事件数据(and its recommended uses are in support of the event implementation conventions. It is certainly possible for a publisher of an event to specify event data without making use of) System.EventArgs
或其任何子类.在这种情况下,委托签名可以指定每个参数类型和名称.但是,这种方法的问题在于,这样的签名将事件发布者与所有订阅者联系在一起.如果将来要修改参数,则所有订阅者也必须进行修改.因此,建议将所有事件数据封装在以下子类中(or any subclass thereof. In such cases, the delegate signature could specify each parameter type and name. The problem with this approach, however, is that such a signature ties the event publisher with all subscribers. If you want to modify the parameters in the future, then all subscribers would have to be modified as well. It is therefore recommended to encapsulate all event data in a subclass of) System.EventArgs
,这样做可以减少随后更改发送给事件订阅者的值的数量和类型所需的工作量.(, as doing so reduces the amount of work required to subsequently change the number and types of values sent to event subscribers.)
为了说明在发送通知时涉及的权衡(To illustrate the tradeoffs involved in sending a) System.EventArgs
子类实例与发送单个事件参数相比,请考虑要添加单个事件的情况(subclass instance vs. sending individual event arguments, consider a scenario in which you want to add a single) string
事件数据的值.如果要将事件数据指定为委托签名中的各个参数(而不是子类化)(value to the event data. If you were to specify your event data as individual parameters in the delegate signature (rather than subclassing) System.EventArgs
),那么您的活动的所有订阅者都必须进行修改以接受其他(), then all subscribers to your event would have to be modified to accept the additional) string
参数.甚至不关心此额外费用的订户(parameter. Even subscribers that do not care about this additional) string
值将不得不修改以接受它,因为自定义事件处理程序签名将被更改.如果有,则将其分类(value would have to be modified to accept it, as the custom event handler signature would be changed. If you had, instead, subclassed) System.EventArgs
,那么您要做的就是添加一个新(, then all you would have to do is add a new) string
属性到您的班级.事件签名不会更改,因此在任何现有订户中使用的事件处理程序签名也不会更改.不关心新订阅者(property to your class. The event signature would not change, and therefore neither would the event handler signature used in any of the existing subscribers. Subscribers that do not care about the new) string
无需触摸属性,因为事件处理程序签名不会更改-他们可以简单地忽略其他属性(property would not have to be touched because the event handler signature would not be changed — and they could simply ignore the additional) string
属性.确实关心新订阅者(property. Subscribers that do care about the new) string
值将能够将其读取为(value would be able to read it as a property of the) EventArgs
子类.(subclass.)
这是一个例子(Here is an example of an) EventArgs
封装单个子类(subclass that encapsulates a single) string
值:(value:)
public class FileDeletedEventArgs : System.EventArgs
{
// Field
string m_FileName = string.empty;
// Constructor
FileDeletedEventArgs(string fileName)
{
m_FileName = fileName;
}
// Property
public string FileName
{
get { return m_FileName; }
}
}
5.3 System.ComponentModel.CancelEventArgs的作用(5.3 The Role of System.ComponentModel.CancelEventArgs)
System.ComponentModel.CancelEventArgs
来自(is derived from) System.EventArgs
,并存在以支持可取消事件.除了提供的成员(, and exists in support of cancellable events. Beyond the members provided by) EventArgs
,(,) CancelEventArgs
提供布尔值(provides the Boolean) Cancel
设置为(property that, when set to) true
由事件订阅者使用,由事件发布者用来取消事件.(by an event subscriber, is used by the event publisher to cancel the event.)
本文的第16节详细介绍了可取消的事件(点击(Section 16 of this article presents cancellable events in greater detail (click) 这里(here) 现在去那里).(to go there now).)
6.事件声明语法(6. Event Declaration Syntax)
6.1事件声明语法替代(6.1 Event Declaration Syntax Alternatives)
的(The) event
关键字用于正式声明一个事件.有两种有效的事件声明语法替代方法.不管您使用哪种语法,C#编译器都会将这两个属性声明转换为输出程序集中的以下三个组件.(keyword is used to formally declare an event. There are two valid event declaration syntax alternatives. Regardless of the syntax you write, the C# compiler will translate both property declarations into the following three components in the output assembly.)
- 私有作用域事件处理程序(或功能等效的数据结构).委托是私有作用域的,以防止外部代码调用事件,从而保留封装.(Privately scoped event handler (or a functionally equivalent data structure). The delegate is privately scoped in order to prevent external code from invoking the event, and thereby preserving encapsulation.)
- 公开范围(publicly scoped)
Add
方法;用于将订阅者添加到私有事件处理程序.(method; used to add subscribers to the private event handler.) - 公开范围(publicly scoped)
Remove
用于从私有事件处理程序中删除订阅者的方法.(method used to remove subscribers from the private event handler.)
1.类字段语法(1. Field-like syntax)
public event TheEventHandler MyEvent;
类似于字段的语法使用一两行代码来声明事件(如果事件不使用内置代码,则一行用于事件,另一行用于关联的事件处理程序)(The field-like syntax declares the event in one or two lines of code (one line for the event, another for the associated event handler — if/when not using a built-in) EventHandler
代表).(delegate).)
2.类属性语法(2. Property-like syntax)
public event TheEventHandler MyEvent
{
add
{
// code here adds the incoming delegate instance to underlying list of
// event handlers
}
remove
{
// code here removes the delegate instance from the underlying list of
// event handlers
}
}
类似于属性的语法看起来与典型的属性声明非常相似,但具有显式的(The property-like syntax appears very similar to a typical property declaration, but with explicit) add
和(and) remove
块代替” getter"和” setter"块.它们没有检索或设置私有成员变量的值,而是向/从基础事件处理程序或充当类似角色的其他数据结构中添加传入代理实例或从中删除传入委托实例.(blocks in place of “getter” and “setter” blocks. Instead of retrieving or setting the value of a private member variable, they add and remove incoming delegate instances to/from the underlying event handler or other data structure that servers a similar role.)
线程注意事项(Threading Considerations)
类似字段的语法自动是线程安全的:(The field-like syntax is automatically thread safe:)
public event FileDeletedHandler FileDeleted;
类似于属性的语法将与创建线程时一样安全.以下是线程安全版本:(The property-like syntax will be as thread safe as you make it. The following is a thread safe version:)
private readonly object padLock = new object();
public event System.EventHandler<filedeletedeventargs />FileDeleted
{
add
{
lock (padLock)
{
FileDeleted += value;
}
}
remove
{
lock (padLock)
{
FileDeleted -= value;
}
}
}
您可以省略(You can omit the) lock{}
块和(blocks and) padLock
如果线程安全无关紧要,则进行变量声明.(variable declaration if thread safety is of no concern.)
6. 2在类字段语法和类属性语法之间进行选择的注意事项(6. 2 Considerations For Choosing Between Field-like Syntax and Property-like Syntax)
在语法备选方案中进行选择时,请考虑与基于字段的语法相比,类似于属性的语法对事件实现的控制更多.尽管类字段语法将被编译为IL,这与为属性类语法生成的IL非常相似,但类字段语法却无法为您提供显式控制事件实现的机会.(When choosing amongst the syntax alternatives, consider that the property-like syntax gives you more control over your event implementation than is available with the field-like syntax. While the field-like syntax will be compiled into IL that very much resembles the IL generated for the property-like syntax, the field-like syntax does not afford you the same opportunities to explicitly control the event implementation.) 使用类似属性的语法,您可以使用事件处理程序(委托)更仔细地控制订户的注册和注销.它还使您能够更轻松,更明确地实现所选的特定锁定机制,以解决线程安全问题.类似于属性的语法还使您能够实现委托以外的自定义事件处理程序机制.您可能想在需要支持许多可能事件的场景中执行此操作,其中只有少数事件在任何给定时间点都有订阅者.在这种情况下,您的事件实现将使用哈希表或类似的数据结构,而不是单个委托来维护所有可能事件和任何关联的侦听器的列表.(Using the property-like syntax enables you to more carefully control the registration and unregistration of subscribers with the event handler (delegate). It also enables you to more easily and explicitly implement the specific locking mechanisms of your choice to address thread safety concerns. The property-like syntax additionally enables you to implement a custom event handler mechanism other than a delegate. You might want to do this in scenarios where you want to support many possible events, only a few of which would have any subscribers at any given point in time. In such a scenario, your event implementation would use a hash table or similar data structure, rather than an individual delegate, to maintain a list of all possible events and any associated listeners.)
6.3使用没有事件的委托的发布/订阅机制(从不这样做)(6.3 Publish/Subscribe Mechanism Using Delegates Without Events (never do this))
应该清楚地理解,事件不是委托-尽管事件在很大程度上取决于委托,并且在某些方面可以看作委托实现的一种形式.即使它们可以以非常相似的方式使用,事件也不是委托实例.(It should be clearly understood that events are not delegates — even though events are very much dependent upon delegates, and in some ways can be seen as a form of a delegate implementation. Events are also not delegate instances, even if they can be used in very similar ways.)
尽管您可以省略event关键字(因此也可以省略事件的正式声明),而只是使用公共委托来提供发布和订阅通知机制,但是您绝对不应这样做.公共委托的问题(与事件声明相比)是,发布类之外的方法可能导致公共范围的委托调用其引用的方法.这违反了基本的封装原理,并且可能是可能难以调试的主要问题(竞赛条件等)的来源.因此,您应该仅通过使用(While you can omit the event keyword (and therefore the formal declaration of the event) and simply use a public delegate to provide a publish-and-subscribe notification mechanism, you should never do so. The problem with public delegates (as compared to event declaration), is that methods outside of the publishing class can cause publicly scoped delegates to invoke their referenced methods. This violates basic encapsulation principles and can be the source of major problems (race conditions, etc) that may be difficult to debug. Consequently you should implement events only through the use of the) event
关键词.如果实现委托以支持事件,则即使将委托声明为定义类的公共成员,也只能从定义类内部调用(通过引发事件),而其他类只能订阅和通过事件取消订阅基础委托.(keyword. When delegates are implemented in support of events, the delegate — even if declared as a public member of the defining class — can only be invoked from within the defining class (via the raising of the event), and other classes can only subscribe to and unsubscribe from the underlying delegate via the event.)
7.活动筹码(7. Event Raising Code)
对于每个事件,发布者应包括一个负责引发事件的受保护的虚拟方法.这将允许子类[更多]轻松访问基类事件.当然,建议将此方法保护为虚拟方法仅适用于未密封类中的非静态事件.(For each event, the publisher should include a protected virtual method that is responsible for raising the event. This will allow subclasses to [more] easily access base class events. Of course the recommendation to make this method protected and virtual applies only to non static events in unsealed classes.)
protected virtual void OnMailArrived(MailArrivedEventArgs)
{
// Raise event here
}
定义事件以及任何关联的委托和发布方法后,发布者将需要引发该事件.引发事件通常应分为两个步骤.第一步是检查是否有任何订户.第二步是引发事件,但前提是有订阅者.(Once an event and any associated delegate and publishing method have been defined, the publisher will need to raise the event. Raising the event should generally be a two step process. The first step would be to check to see if there are any subscribers. The second step is to raise the event, but only if there are any subscribers.)
如果没有订阅者,则代表将测试(If there are no subscribers, then the delegate will test to) null
.以下逻辑引发该事件,但前提是该事件具有任何订阅者.(. The following logic raises the event, but only if the event has any subscribers.)
if (MyEvent != null)
{
MyEvent(this, EventArgs.Empty);
}
测试之间可能会(通过在另一个线程中执行的代码)清除事件.(There is a possibility that the event could be cleared (by code executing in another thread) between the test for) null
和实际引发事件的那条线.这种情况构成了竞争条件.因此,建议创建,测试和引发事件的事件处理程序(委托)的副本,如下所示:(and the line that actually raises the event. This scenario constitutes a race condition. So it is recommended to create, test, and raise a copy of the event’s event handler (delegate), like this:)
MyEventHandler handler = MyEvent;
if (handler != null)
{
handler (this, EventArgs.Empty)
}
订户中事件处理方法中引发的任何未处理的异常都将传播到事件发布者.因此,仅应在(Any unhandled exceptions raised in the event handling methods in subscribers will be propagated to the event publisher. The raising of the event should therefore be attempted only within a) try/catch
块:(block:)
public void RaiseTheEvent(MyEventArgs eventArgs)
{
try
{
MyEventHandler handler = MyEvent;
if (handler != null)
{
handler (this, eventArgs)
}
}
catch
{
// Handle exceptions here
}
}
事件可以有多个订阅者-当事件处理程序由[调用时,每个事件依次由事件处理程序(委托)调用.(Events can have multiple subscribers — each of which is called, in turn, by the event handler (delegate) when the event handler is invoked by the [) handler (this, eventArgs)
]行.当订户引发第一个未处理的异常时,以上代码块中使用的事件处理程序将停止对(调用的事件处理方法的)调用列表进行迭代.因此,例如,如果有3个订阅者,而第二个订阅者在委托调用时抛出未处理的异常,则第3个订阅者将永远不会收到事件通知.如果您希望每个订阅者都收到事件通知,即使其他订阅者抛出未处理的异常,则可以使用以下逻辑来显式循环遍历事件处理程序的调用列表:(] line. The event handler used in the above block of code would stop iterating over it’s invocation list (of subscribed event handling methods) when the first unhandled exception is raised by a subscriber. So, if there were 3 subscribers, for example, and the 2nd one threw an unhandled exception when invoked by the delegate, then the 3rd subscriber would never receive the event notification. If you want for every subscriber to receive the event notification even if other subscribers throw unhandled exceptions, then you could use the following logic which explicitly loops through the event handler’s invocation list:)
public void RaiseTheEvent(MyEventArgs eventArgs)
{
MyEventHandler handler = MyEvent;
if (handler != null)
{
Delegate[] eventHandlers = handler.GetInvocationList();
foreach (Delegate currentHandler in eventHandlers)
{
MyEventHandler currentSubscriber = (MyEventHandler)currentHandler;
try
{
currentSubscriber(this, eventArgs);
}
catch (Exception ex)
{
// Handle exception here.
}
}
}
}
8.事件订阅者的注册和注销(8. Event Subscriber Registration and Unregistration)
通过设计,事件的发布者绝对不了解任何订阅者.因此,订阅者的工作是向事件的发布者注册或注销自己.(By design, the publisher of an event has absolutely no knowledge of any of the subscribers. Consequently, it is the job of subscribers to register or unregister themselves with the publisher of an event.)
8.1注册订户(8.1 Registering A Subscriber)
为了订阅事件,订阅者需要三件事:(In order to subscribe to an event, the subscriber needs three things:)
- 对发布感兴趣事件的对象的引用(a reference to the object publishing the event of interest)
- 定义事件的委托的实例(an instance of the delegate upon which the event is defined)
- 发布者引发事件时将调用的方法(a method that will be called by the publisher when it raises the event) 然后,订阅者向发布者注册其事件处理程序(委托)实例,如下所示:(The subscriber then registers its event handler (delegate) instance with the publisher, like this:)
thePublisher.EventName += new
MyEventHandlerDelegate(EventHandlingMethodName);
在上面的行中…(In the above line…)
thePublisher
是对引起关注事件的对象的引用.注意事件如何(is the reference to the object that will raise the event of interest. Notice how the event,)EventName
,就像它是的公共财产一样被访问(, is accessed as if it were a public property of)thePublisher
.(.)- 的(The)
+=
运算符用于将委托实例添加到发布者中事件处理程序的调用列表中.请记住,多个订阅者可以注册该事件.使用+ =运算符可将当前订户追加到基础委托的调用列表中.(operator is used to add the delegate instance to the invocation list of the event handler in the publisher. Remember, multiple subscribers may register with the event. Use the += operator to append the current subscriber to the underlying delegate’s invocation list.) MyEventHandlerDelegate
是对要使用的特定事件处理程序委托的引用(如果不是内置的(is a reference the particular event hander delegate to be used (if not one of the built-in)EventHandler
代表).(delegates).)- 最后(Finally)
EventHandlingMethodName
在事件引发时将在订阅类中提供方法的名称.(supplies the name of the method in the subscribing class that is to be called upon the raising of the event.) 警告(WARNING):请勿使用(: Do not use the)=
向发布者注册事件订阅者时的操作员.这样做将用当前订户替换任何/所有当前注册的事件订户.相反,请务必使用(operator when registering an event subscriber with a publisher. Doing so would replace any/all currently registered event subscribers with the current subscriber. Instead, be sure to use the)+=
运算符,以将当前订户添加到事件处理程序的调用列表中.(operator to cause the current subscriber to be appended to the event handler’s invocation list.)
8.2注销订户(8.2 Unregistering A Subscriber)
订阅者可以从发布者注销,如下所示:(A subscriber can unregister from the publisher, like this:)
thePublisher.EventName -=
EventHandlerDelegate(EventHandlingMethodName);
的(The) -=
运算符用于从发布者的调用列表中删除委托实例.(operator is used to remove the delegate instance from the invocation list in the publisher.)
处置对象时,如果尚未从事件中显式取消注册订阅者,则订阅者将自动注销.(Subscribers are automatically unregistered when an object is disposed — if the subscriber was not already explicitly unregistered from the event.)
9.事件处理方法(9. Event Handling Method)
事件处理方法是事件订阅服务器中由事件发布者在引发事件时执行的方法.请注意,尽管从技术上讲精确地说,“事件处理程序"是事件所基于的委托,但从技术上讲,.NET中描述事件的一些文献将这些方法称为"事件处理程序”,而不是此类事件引用的任何方法一位代表.(An event handling method is the method in an event subscriber that is executed by the event publisher upon the raising of an event. Be aware that some literature describing events in .NET refers to these methods as “event handlers” even though, to be technically precise, an “event handler” is a delegate upon which an event is based — and not any method referenced by a such a delegate.) 事件处理方法的重要要求是其签名必须与定义事件的事件处理程序(委托)的签名匹配.(The important requirement of the event handling method is that its signature must match the signature of the event handler (delegate) upon which the event is defined.) 您还应该仔细考虑事件处理方法中可能引发或捕获的任何异常的后果.未在事件处理方法中捕获的异常将传播到事件发布者.(You should also carefully consider the consequences of any exceptions that may be thrown or caught in the event handling method. Exceptions not caught in the event handling method will propagate to the event publisher.)
10. .NET 1.x与2.0+注意事项(10. .NET 1.x vs. 2.0+ Considerations)
本节介绍的概念和功能是在.NET Framework 2.0版中引入的.这些较新的功能不仅可以简化操作,而且可以巧妙地使用代码,从而简化代码.(The concepts and features presented in this section were introduced in the 2.0 version of the .NET Framework. These newer features amount to shortcuts and, possibly, the simplification of your code when used smartly.) 但是,如果不正确使用其中的某些功能,则可能会使事件实现代码更难以理解.例如,如果您使用由30余行代码组成的"匿名方法”(如下所示),则事件实现可能比等价的实现更难读,而后者将30+命名方法中的行.(There is a risk, however, that the improper use of some of these features could make your event implementation code more difficult to understand. For example, if you made use of an “anonymous method” (presented below) that was comprised of 30+ lines of code, your event implementation would likely be far more difficult to read than an equivalent implementation that, instead, places those 30+ lines in a named method.) 重要的是要理解,这些2.0+概念和功能不会对.NET Framework应用程序中实现事件的方式带来任何根本改变.相反,它们主要旨在简化我们实施事件的方式.(It is important to understand that these 2.0+ concepts and features do not present any fundamental change to the way events are implemented in .NET Framework applications. Instead, they are mostly intended to simplify the way we go about implementing events.)
10.1泛型(10.1 Generics)
除了本文其他地方介绍的特定于通用的功能(例如,(In addition to the Generic-specific features presented elsewhere in this article (e.g.,) System.EventHandler<T>
),应注意的是,任何以任何方式依赖于泛型的事件实现技术都不会在.NET 1.x应用程序中使用,因为泛型最初是在.NET Framework 2.0版中引入的.(), it should be noted that any event implementation techniques that rely in any way on Generics will not be available in .NET 1.x applications, as Generics were first introduced in the 2.0 version of the .NET Framework.)
10.2委托推理(10.2 Delegate Inference)
C#2.0(及更高版本)的编译器足够聪明,可以确定实现特定事件的委托类型.此"委托推论"功能使您可以省略在向事件注册事件处理方法的代码中必需委托的声明.(The C# 2.0 (and newer) compiler is smart enough to determine the type of delegate with which a particular event is implemented. This “delegate inference” capability enables you to omit the declaration of the requisite delegate in the code that registers an event handling method with an event.) 考虑下面的1.x代码,该代码向事件注册了事件处理方法.此代码显式实例化事件处理程序(委托),以便将关联的方法注册到事件.(Consider the following 1.x code that registers an event handling method with an event. This code explicitly instantiates the event handler (delegate) in order to register the associated method with the event.)
thePublisher.EventName += new MyEventHandlerDelegate(EventHandlingMethodName);
以下2.0+代码使用委托推论向事件注册相同的方法.请注意,以下代码出现了,以便直接向事件注册事件处理方法.(The following 2.0+ code uses delegate inference to register the same method with the event. Notice the following code appears to register the event handling method directly with the event.)
thePublisher.EventName += EventHandlingMethodName;
像这样直接将方法名称分配给事件时,C#编译器确保方法签名与事件所基于的事件处理程序的签名匹配.然后,C#编译器插入必要的委托注册代码(即,(When you assign the method name directly to the event like that, the C# compiler ensures that the method signature matches the signature of the event handler upon which the event is based. The C# compiler then inserts the requisite delegate registration code (i.e.,) ... += new MyEventHandlerDelegate(EventHandlingMethodName);
)在输出部件中.() in the output assembly.)
C#编译器可以实现这种简化的语法,而无需更改.NET Framework中实现事件的基本方式.要明确的是(This simplified syntax is made possible by the C# compiler, and not by any change to the fundamental ways that events are implemented in the .NET Framework. To be clear, it is)不(not)C#2.0(及更高版本)中的事件可以直接引用方法的情况.编译器为我们所做的是在输出程序集中提供[仍然]必需的委托语法-就像我们已显式实例化委托一样.(the case that events in C# 2.0 (and newer) can directly reference methods. What the compiler is doing for us is supplying the [still] requisite delegate syntax in the output assembly — as if we had explicitly instantiated the delegate.)
10.3匿名方法(10.3 Anonymous Methods)
匿名方法是您传递给委托的代码块(而不是传递委托要引用的方法的名称).当C#编译器遇到匿名方法时,它将在包含您提供的代码块的输出程序集中创建一个完整的方法.编译器为该方法提供一个名称,然后从关联的委托实例中引用该[new]方法(所有这一切都发生在输出程序集中).之所以称该方法为"匿名",是因为您在不知道其名称的情况下使用它(源代码中没有名称).(An anonymous method is a block of code that you pass to a delegate (rather than passing the name of a method to be referenced by the delegate). When the C# compiler encounters an anonymous method, it creates a complete method in the output assembly that contains the code block you supplied. The compiler supplies a name for the method, then references that [new] method from the associated delegate instance (all this happens in the output assembly). The method is said to be “anonymous” because you are making use of it without knowing its name (it has no name in your source code).) 匿名方法为您提供了编写更简单代码的机会.考虑以下代码,该代码向事件注册了简短的事件处理方法:(Anonymous methods present an opportunity for you to write simpler code. Consider the following code that registers a short event handling method with an event:)
static void EventHandlingMethod(object sender, EventArgs e)
{
Console.WriteLine("Handled by a named method");
}
thePublisher.EventName += new MyEventHandlerDelegate(EventHandlingMethod);
可以使用匿名方法重写以上逻辑,如下所示:(The above logic can be rewritten with an anonymous method, like this:)
thePublisher.EventName += delegate {
Console.WriteLine("Handled by anonymous method");
};
匿名方法旨在简化我们的代码.当代码块相对较短时,可以进行这种简化.在上面的示例中,使用匿名方法语法的逻辑版本很容易阅读,因为我们不必定位任何单独的事件处理方法即可了解订户在引发事件时将如何响应.但是,在代码块由多行代码组成的情况下,匿名方法的语法可能更麻烦阅读(比引用命名方法的逻辑更麻烦).一些作者建议,包含多于三或四行代码的代码块不应实现为匿名方法.相反,这些较长的代码块应进入命名方法中,以提高可读性.(Anonymous methods are intended to simplify our code. This simplification can happen when the code block is relatively short. In the above example, the version of the logic that uses the anonymous method syntax is easy to read because we don’t have to locate any separate event handling method in order to understand how the subscriber will respond when the event is raised. The anonymous method syntax can, however, be more cumbersome to read (more so than logic that references a named method) in cases where the code block is comprised of many lines of code. Some authors suggest that code blocks containing more than three or four lines of code should not be implemented as anonymous methods. These more lengthy code blocks should, instead, go into named methods in order to improve readability.) 总结到目前为止提出的替代方案,以下代码演示了用于向事件注册事件处理方法的三个选项.第一个演示了适用于所有版本的.NET Framework的显式方法.第二个演示委托推理.第三部分演示了匿名方法的使用:(To summarize the alternatives presented so far, the following code demonstrates three options for registering an event handling method with an event. The first demonstrates the explicit approach that works with all versions of the .NET Framework. The second demonstrates delegate inference. The third demonstrates the use of an anonymous method:)
// Option 1 - explicit delegate creation with a named method
thePublisher.EventName += new MyEventHandlerDelegate(EventHandlingMethod);
// Option 2 - delegate inference
thePublisher.EventName += EventHandlingMethod;
// Option 3 - anonymous method
thePublisher.EventName += delegate(object sender, EventArgs e) {
Console.WriteLine("handled by anonymous method");
// You can access the sender and e parameters here if necessary
};
// Event handling method used in options 1 and 2
static void EventHandlingMethod(object sender, EventArgs e)
{
Console.WriteLine("Handled by a named method");
}
10.4局部类(10.4 Partial Classes)
局部类与事件的实现有关,因为Visual Studio会将事件注册代码和事件处理方法存根放置在与给定Windows Form类关联的局部类文件中.请点击(Partial classes are relevant to the implementation of events in that Visual Studio will place event registration code and event handling method stubs in the partial class files associated with a given Windows Form class. Click) 这里(here) 请转到第15.1节,该节更详细地介绍了部分类中事件的实现.(to go to section 15.1 which presents the implementation of events in partial classes in greater detail.)
11.约定(11. Conventions)
从许多资源中收集了以下约定,包括.NET Framework的作者和其他知名的行业专家(有关完整列表,请参见本文结尾处的参考列表).(The following conventions were gleaned from a number of resources, including the authors of the .NET Framework and other well-known industry experts (see reference list at the end of this article for the complete list).)
11.1事件发布者约定(11.1 Event Publisher Conventions)
活动名称(Event Name)
-
选择一个可以清楚传达事件表示状态变化的名称.(Choose a name that clearly communicates the state change the event represents.)
-
事件可分类为(1)状态更改发生之前引发的事件; (2)状态变更后引发的事件.因此,应该选择事件名称以反映事件的[状态更改]之前或之后.(Events can be categorized as (1) events raised before a state change occurs; and (2) events raised after a state change occurs. Consequently, the event name should be chosen to reflect the before or after [state change] aspect of the event.) 示例-状态更改之前引发的事件:(Examples — for events raised before state change:)
-
FileDownloading
-
TemperatureChanging
-
MailArriving
示例-状态更改后引发的事件:(Examples — for events raised after state change:) -
FileDownloadCompleted
-
TemperatureChanged
-
MailArrived
System.EventArgs子类(如果适用)(System.EventArgs Subclass (where applicable))
-
如果事件必须或可能[在某天]携带自定义事件数据,则应创建一个新类,该类应扩展(1)(In the case of events that must or may [someday] carry custom event data, you should create a new class that (1) extends)
System.EventArgs
,并且(2)实现包含和公开您的自定义事件数据所需的成员(例如属性)(, and (2) implements the members (e.g., properties) required to contain and expose your custom event data) -
唯一不应该子类化的时间(The only time you should not subclass)
EventArgs
当您确定您的活动永远不会携带活动数据时(is when you are certain that your event will never carry event data) -
你的名字(The name of your)
EventArgs
子类应该是事件的名称,并附加" EventArgs"(subclass should be the name of the event, with ‘EventArgs’ appended) 例(Example)EventArgs
子类名称:(subclass names:) -
DownloadCompletedEventArgs
-
TemperatureChangedEventArgs
-
MailArrivedEventArgs
对于不包含数据且永远不会包含数据的事件,建议通过(For events that contain no data, and never will, it is recommended to pass)System.EventArgs.Empty
.推荐的做法有助于维护事件实现约定,即使对于没有事件数据的事件也是如此.如果您的事件有一天可能会携带事件数据(即使不是在最初实施时),那么您应该创建一个子类(. This recommended practice serves to maintain the event implementation conventions even for events that have no event data. If there is a possibility that your event may someday carry event data, even if not at initial implementation, then you should create a subclass of)System.EventArgs
,并在您的事件中使用它.此建议的主要好处是,有一天您将能够向子类添加数据(属性)而不会破坏与现有订阅者的兼容性.(, and use that in your events. The primary benefit of this recommendation is that you will someday be able to add data (properties) to your subclass without breaking compatibility with existing subscribers.)
事件处理程序(委托)名称(Event Handler (delegate) Name)
-
如果使用的是.NET Framework 1.x,则应使用内置的(If you are using .NET Framework 1.x, then you should use the built-in)
System.EventHandler
代表(delegate) -
如果您使用的是.NET Framework 2.0或更高版本(面向"发布者和订阅者"),则可以使用通用(If you are using .NET Framework 2.0 or newer (for ‘both’ publishers and subscribers), then you can make use of the generic)
System.EventHandler<TEventArgs>
代表(delegate) -
如果创建自己的委托,则委托名称应由事件名称组成,并附加单词" Handler"(If you create your own delegate, then the delegate name should be comprised of the event name, with the word, ‘Handler’ appended) 示例自定义事件处理程序(委托)名称:(Example custom event handler (delegate) names:)
-
DownloadCompletedHandler
-
TemperatureChangedHandler
-
MailArrivedHandler
事件处理程序(委托)签名(Event Handler (delegate) Signature)
如上所述,在"代理名称"下,您应使用以下一种(As stated above, under “Delegate Name,” you should use one of the) System.EventHandler
.NET Framework提供的委托.在这些情况下,当然可以为您确定委托人签名,并自动遵循推荐的约定.(delegates provided by the .NET Framework. In these cases, the delegate signature is, of course, determined for you and automatically conforms to the recommended conventions.)
以下建议已在(The following recommendations are implemented in the) System.EventHandler
.NET Framework提供的委托.如果创建自己的事件处理程序,则应遵循以下建议,以与.NET Framework的实现保持一致.(delegates provided by the .NET Framework. If you create your own event handlers, then you should follow these recommendations in order to remain consistent with the .NET Framework’s implementation.)
-
代表应始终返回(The delegate should always return)
void
.(.)对于事件处理程序,将值返回给事件发布者根本没有任何意义.请记住,事件发布者在设计上并不了解其订阅者.实际上,根据设计,委托人充当事件发布者及其订阅者之间的中介.因此,发布者不应该对订户有任何了解,包括接收返回值的可能性.委托正在调用每个订阅者,因此任何返回值都将使其仅到达委托者,并且永远不会到达发布者.对于避免使用两个(In the case of event handlers, it simply makes no sense to return a value to the event publisher. Remember, event publishers, by design, have no knowledge of their subscribers. In fact the delegate, by design, acts as an intermediary between the event publisher and its subscribers. Consequently, publishers aren’t supposed to know anything about their subscribers — including the possibility of receiving returned values. It is the delegate that is calling each subscriber, so any return value would make it only as far as the delegate and would never get to the publisher anyway. This rationale holds true for avoiding output parameters that use either the)out
要么(or)ref
参数修饰符.订阅者的输出参数将永远不会传播到发布者.(parameter modifiers. The output parameters of the subscribers would never propagate to the publisher.) -
第一个参数应该是(The first parameter should be of the)
object
类型,应命名(type and should be named)sender
.(.)第一个参数是保存对引发事件的对象的引用.通过将引用传递给事件发布者,事件订阅者可以在给定事件的多个可能发布者之间进行区分.如果没有对发布者的引用,则事件处理方法将无法识别引发特定事件的特定发布者或对其采取行动.(This first parameter is to hold a reference to the object that raises the event. Passing a reference to the event publisher enables the event subscriber to distinguish amongst multiple possible publishers of a given event. Without a reference to the publisher, the event handling method would have no way to identify or act on the specific publisher that raised a particular event.) 的数据类型(The data type for)sender
之所以成为对象是因为几乎任何类都可以引发事件.避免强类型(is object because practically any class can raise events. Avoiding a strongly-typed)sender
参数允许在所有事件中采用一个一致的事件处理程序签名.在必要时,事件处理方法可以将sender参数转换为特定的事件发布者类型.(parameter allows for one consistent event handler signature to be employed across all events. Where necessary, the event handling method can cast the sender parameter to the specific event publisher type.) 静态事件应该通过(Static events should pass)null
作为发件人的价值,而不是忽略(as the value of sender, rather than omitting the)sender
参数.(parameter.) -
第二个参数应命名为"(The second parameter should be named ‘) `` ‘并且应该是(’ and should be of the)
System.EventArgs
类型或您的自定义子类(type or your custom subclass of)System.EventArgs
(例如.,((e.g.,)MailArrivedEventArgs
).().)在可取消事件的情况下,第二个参数是(In the case of cancellable events, the second parameter is either of the)System.ComponentModel.CancelEventArgs
类型或您的自定义子类.如果事件不包含事件数据,则应指定(type or your custom subclass thereof. In the case of events that carry no event data, you should specify)System.EventArgs
作为第二个参数类型.在这种情况下(as the second parameter type. In such cases)System.EventArgs.Empty
引发事件时,指定为此参数的值.建议采用这种做法来保持对约定的遵守-以便所有事件处理程序签名都包含一个(is specified as the value of this parameter when the event is raised. This practice is recommended to maintain conformance to the convention — so that all event handler signatures include an)EventArgs
参数-即使对于没有事件的事件(parameter — even for events that do not have)EventArgs
.显然,按照惯例,即使在永远不会使用参数之一的情况下,拥有一个一致的签名也比拥有多个事件处理程序签名更为重要.(. Apparently, according to the convention, having one consistent signature is more important than having multiple event handler signatures — even in cases where one of the parameters will never be used.)
示例(事件未发送自定义数据):(Examples (with no custom data sent with the event):)
delegate void DownloadCompletedHandler(object sender, EventArgs e);
delegate void TemperatureChangedHandler (object sender, EventArgs e);
delegate void MailArrivedHandler (object sender, EventArgs e);
示例(随事件发送的自定义数据):(Examples (with custom data sent with the event):)
delegate void DownloadCompletedHandler(object sender,
DownloadCompletedEventArgs e);
delegate void TemperatureChangedHandler (object sender,
TemperatureChangedEventArgs e);
delegate void MailArrivedHandler (object sender,
MailArrivedEventArgs e);
活动声明(Event Declaration)
- 假设该事件可供发布类之外的代码使用,则该事件将使用(Assuming the event is to be made available to code outside of the publishing class, the event would be declared with the)
public
关键字(使发布类外部的代码可以访问它).(keyword (to make it accessible to code outside of the publishing class).) - 事件所基于的事件处理程序被指定为(The event handler upon which the event is based is specified as the)**类型(type)**事件的类型—以类似的方式在典型的属性或方法声明中指定数据类型.(of the event — in a similar fashion with which a data type is specified in a typical property or method declaration.)
示例(使用内置的泛型(Example (uses built-in generic)
System.EventHandler<TEventArgs>
代表):(delegate):)
public event System.EventHandler<mailarrivedeventargs> MailArrived;
示例(使用自定义事件处理程序):(Example (makes use of a custom event handler):)
public delegate void MailArrivedHandler (object sender,
MailArrivedEventArgs e);
public event MailArrivedHandler<mailarrivedeventargs> MailArrived;
引发事件的方法(Method That Raises the Event)
- 建议不要在整个代码中内联引发事件,而建议创建一个单独的方法来引发事件.然后,根据需要在整个代码中调用该方法.(Rather than raising an event inline throughout your code, it is recommended to create a separate method that is responsible for raising the event. You then call that method throughout your code as necessary.)
- 此方法的名称应为单词(The name of this method should be the word)
On
附加事件名称.(with the event name appended.) - 如果您的活动使用自定义(If your event makes use of a custom)
EventArgs
子类,则引发事件的方法应接受至少一个特定的参数(subclass, then the method that raises the event should accept at least one parameter that is of the particular)EventArgs
为自定义事件数据定义的子类.(subclass defined for the custom event data.) - 对于未密封的非静态类,该方法应实现为(For non-static classes that are not sealed, the method should be implemented as)
virtual
可访问性指定为(with accessibility specified as)protected
以便派生类可以轻松通知已向基类注册的客户端.(so that derived classes can easily notify clients registered with the base class.) - 对于密封类,方法的可访问性当然应该设置为(For sealed classes the accessibility of the method should of course be set to)
private
,因为事件的引发不应从课堂之外发起.(, as the raising of events should not be initiated from outside of the class.) 示例(每个都包含一个自定义(Examples (each takes a custom)EventArgs
子类类型作为参数):(subclass type as an argument):)
OnDownloadCompleted(DownloadCompletedEventArgs)
{
// Raise event here
}
private OnTemperatureChanged(TemperatureChangedEventArgs)
{
// Raise event here
}
virtual OnMailArrived(MailArrivedEventArgs)
{
// Raise event here
}
11.2事件订阅者约定(11.2 Event Subscriber Conventions)
事件处理方法名称(Event Handling Method Name)
-
Visual Studio在自动创建事件处理方法存根时实现的约定是将该方法命名为(1)引发事件的对象的名称;其次是(2)下划线;加上(3)事件名称.(The convention implemented by Visual Studio, when it automatically creates an event handling method stub, is to name the method as (1) the name of the object raising the event; followed by (2) an underscore character; with (3) the event name appended.) 例子:(Examples:)
-
downloader_DownloadCompleted
-
weatherStation_TemperatureChanged
-
mailManager_OnMailArrived
-
用于确定事件处理方法的名称的另一种约定与上述用于指定引发发布者中的事件的方法的名称的约定相同.具体来说,方法的名称应为单词(Another convention for determining the name of the event handling method is the same as that described above for specifying name of the method that raises the event in the publisher. Specifically, the name of the method should be the word)
On
附加事件名称.(with the event name appended.) 例子:(Examples:) -
OnDownloadCompleted
-
OnTemperatureChanged
-
OnMailArrived
事件处理方法签名(Event Handling Method Signature)
- 事件处理方法的签名必须与委托签名完全匹配.根据事件处理约定,以及(The signature of the event handling method must exactly match the delegate signature. According to the event handling conventions, as well as the)
EventHandler
.NET Framework提供的委托,事件处理方法必须返回(delegates provided by the .NET Framework, the event handling method must return)void
,同时恰好接受两个参数:(, while accepting exactly two parameters: an)object
类型的变量名为(-typed variable named)sender
, 和(, and an)EventArgs
(或派生类)名为"((or derived class) instance named ‘) `` ‘.(’.) 例子:(Examples:)
void DownloadManager_DownloadCompleted(object sender,
DownloadCompletedEventArgs e)
{
// event handling code goes here
}
void WeatherStation_TemperatureChanged(object sender,
TemperatureChangedEventArgs e)
{
// event handling code goes here
}
void MailMonitor_MailArrived(object sender, MailArrivedEventArgs e)
{
// event handling code goes here
}
订阅事件(向事件注册事件处理方法的代码)(Subscribing to the Event (code that registers the Event Handling Method with the Event))
- 要向事件注册方法,请使用(To register a method with an event, use the)
+=
语法,根据此模式:(syntax, according to this pattern:)EventPublisherObject.EventName+=new EventHandlerDelegateName(NameOfMethodToCall);
例:(Example:)
m_MailMonitor.MailArrived += new EventHandler(
this.MailMonitor_MailArrived);
警告(WARNING):请勿使用(: Do not use the) =
向发布者注册事件订阅者时的操作员.这样做将用当前订户替换任何/所有当前注册的事件订户.相反,请务必使用(operator when registering an event subscriber with a publisher. Doing so would replace any/all currently registered event subscribers with the current subscriber. Instead, be sure to use the) +=
运营商以使当前订户成为(operator to cause the current subscriber to be)**附加的(appended)**到事件处理程序的调用列表.(to the event handler’s invocation list.)
取消订阅事件(从事件中注销事件处理方法的代码)(Unsubscribing from the Event (Code that Unregisters the Event Handling Method from the Event))
- 要注销带有事件的方法,请根据以下模式使用-=语法:(To unregister a method with an event, use the -= syntax, according to this pattern:)
EventPublisherObject.EventName-=new EventHandlerDelegateName(NameOfMethodToCall);
例:(Example:)
m_MailMonitor.MailArrived -= new EventHandler(
this.MailMonitor_MailArrived);
11.3命名约定(11.3 Naming Conventions)
骆驼外壳(Camel Casing)
驼峰式大小写是一个命名约定,其中第一个字母为小写,随后的每个"单词部分"均以大写字母开头.按照惯例,变量名使用驼峰式大小写.(Camel casing is a naming convention whereby the first letter is lower case, with each subsequent “word part” starting with an upper case letter. By convention, variables names are camel cased.)
骆驼案例:(Camel cased examples:) someStringToWrite, ovenTemperature, latitude
帕斯卡套管(Pascal Casing)
Pascal大小写是命名约定,其中名称的每个"单词部分"均以大写字母开头,其他字母均以小写字母开头,并且不包含下划线.按照约定,类,事件,委托,方法和属性的名称应使用Pascal大小写.(Pascal casing is naming convention whereby every “word part” of a name starts with an upper case letter, with other letters lower case, and no underscores. By convention, names of classes, events, delegates, methods, and properties are to be Pascal cased.)
Pascal案例:(Pascal cased examples:) MailArrivedEventHandler, AppClosing, MyClassName
12.创建自定义事件的步骤(12. Steps to Creating Custom Events)
为了使以下步骤尽可能简短,很少或不提供任何给定步骤的解释.每个步骤的解释,示例和约定都在本文的其他地方介绍.(In order to keep the following steps as brief as possible, little or no explanation of any given step is provided. Explanations, examples, and conventions for each step are presented elsewhere throughout this article.)
12.1准备事件发布者(12.1 Prepare the Event Publisher)
第1步:EventArgs-确定您的事件将如何占EventArgs.(Step 1: EventArgs - Decide how your event will account for EventArgs.)
- 包含(Including)
EventArgs
具有自定义事件的事件必须符合事件发布标准.(with custom events is required to conform to the event publishing standards.)EventArgs
但是,这不是技术要求-您可以创建,引发和处理不使用任何形式的自定义事件(, however, are not a technical requirement — you can create, raise, and handle custom events that do not make any use of)EventArgs
.(.) - 如果您的事件永远不会传递自定义事件数据,那么您可以通过决定使用内置事件来满足此步骤(If your event will never communicate custom event data, then you can satisfy this step by deciding to use the built-in)
System.EventArgs
类.您稍后将指定该值,(class. You would later specify the value,)EventArgs.Empty
,在引发事件时.(, when raising the event.) - 如果您的事件不可取消,并且包含自定义事件数据,则应创建一个扩展(If your event is not cancellable and includes custom event data, then you should create a class that extends)
System.EventArgs
.您的习惯(. Your custom)EventArgs
子类将包含包含事件数据的任何其他属性.(subclass would include any additional properties that contain the event data.) - 如果您的活动可以取消,则可以使用(If your event is cancellable then you can use)
System.ComponentModel.CancelEventArgs
—包括布尔值(— which includes the Boolean)Cancel
客户端可以将其设置为true来取消事件的属性.您可以创建的子类(property that clients can set to true to cancel the event. You can create a subclass of)CancelEventArgs
具有任何其他特定于事件的数据的属性.(that has properties for any additional event-specific data.)
步骤2:事件处理程序-决定您的事件将使用哪个事件处理程序.(Step 2: Event Handler - Decide which event handler your event will use.)
- 您有两种基本选择-创建自己的事件处理程序(委托)或使用以下一种(You have two basic alternatives — create your own event handler (delegate) or use one of the)
EventHandler
.NET Framework提供的委托.如果您使用内置事件处理程序之一,则将需要维护的代码更少,并且事件处理程序签名将自动符合返回的约定(delegates provided by the .NET Framework. If you use one of the built-in event handlers, then you will have less code to maintain and your event handler signature will automatically conform to the convention of returning)void
,同时接受参数,(, while accepting the parameters,)object sender
和(, and)EventArgs e
- 如果使用.NET 1.x,请考虑使用内置(If using .NET 1.x, consider using the built-in)
System.EventHandler
代表.(delegate.) - 如果使用.NET 2.0,请考虑使用内置的泛型(If using .NET 2.0, consider using the built-in generic)
System.EventHandler<TEventArgs>
代表.(delegate.)
步骤3:声明事件—决定使用哪种语法:类似字段的语法或类似属性的语法.(Step 3: Declare the Event — Decide which syntax to use: field-like syntax or property-like syntax.)
- 类似字段的语法足以满足许多自定义事件的实现.(The field-like syntax will suffice for many custom event implementations.)
- 当您的类暴露大量事件时,请考虑使用类似于属性的语法,在任何给定时间都应预订其中的少数事件.(Consider going with the property-like syntax when your class exposes a large number of events, only a few of which are expected to be subscribed to at any given time.)
步骤4:事件引发方法-决定是从方法引发事件还是内联引发事件.(Step 4: Event-Raising Method — Decide whether you will raise the event from a method, or raise it inline.)
- 通常建议从专用于该任务的方法引发事件,而不是内联和在整个代码中引发事件.(It is generally recommended to raise events from a method that is dedicated to that task, rather than raising events inline and throughout your code.)
第5步:引发事件.(Step 5: Raise the event.)
- 内联引发事件,或调用引发事件的方法.(Either raise the event inline, or call the method that raises the event.)
- 在发起活动之前,您需要先拥有一个实例(Prior to raising the event you will need to have an instance of your)
EventArgs
子类填充了特定于事件的数据.如果不利用任何(subclass populated with event-specific data. If not making use of any)EventArgs
子类,那么您应该包括(subclass, then you should include)System.EventArgs.Empty
代替风俗(in place of a custom)EventArgs
引发该方法时的类.(class when you raise the method.)
12.2准备事件订阅者(12.2 Prepare the Event Subscriber)
因为本文介绍了事件模式((Because this article presents the event pattern () object sender, EventArgs e
)在.NET Framework类中实现了以下步骤,除了按照相同模式创建的自定义事件之外,以下步骤还可以帮助您连接实际上可用于所有.NET Framework事件的事件处理方法.() implemented throughout the .NET Framework classes, the following steps will help you wire up event handling methods that work with practically all .NET Framework events, in addition to custom events you create according to the same pattern.)
步骤1:编写事件处理方法.(Step 1: Write the Event Handling Method.)
- 用签名与定义该方法的委托人完全匹配的签名定义事件处理方法.(Define an event handling method with a signature that exactly matches the delegate upon which the method is defined.)
- 使用内置的非泛型(When using either the built-in non generic)
System.EventArgs
或通用(, or the generic)System.EventHandler<TEventArgs>
在事件声明中委托,结果签名自动匹配返回约定(delegate in the event declaration, the resulting signature automatically matches the convention of returning)void
并接受参数((and accepting the parameters ()object sender, EventArgs e
).().)
步骤2:实例化事件发布者.(Step 2: Instantiate the Event Publisher.)
- 声明引用发布感兴趣事件的类或对象的类级别成员变量.(Declare class-level member variable that references the class or object that publishes the event of interest.)
步骤3:实例化事件处理程序(如有必要).(Step 3: Instantiate the event handler (if necessary).)
- 如果感兴趣的事件基于自定义事件处理程序,则创建该事件处理程序的实例,并传入事件处理方法的名称.(If the event of interest is based on a custom event handler, then create an instance of that event handler, passing in the name of the event handling method.)
- 通过使用new关键字将委托与事件注册到的同一行中的委托实例化,可以将该步骤与步骤4(下一个)组合在一起.(This step may be combined with Step 4 (next) by using the new keyword to instantiate the delegate in the same line in which the delegate is registered with the event)
步骤4:向事件注册订户(事件处理方法).(Step 4: Register the subscriber (event handling method) with the event.)
- 任何.NET版本:(Any .NET version: Use the)
+=
向事件注册事件处理程序的语法.(syntax to register the event handler with the event.) - .NET 2.0+:或者,通过委托推断,您可以直接将方法名称直接分配给事件.(.NET 2.0+: Alternatively, through delegate inference, you can simply assign the method name directly to the event.)
- .NET 2.0+:或者,如果事件处理方法非常简短(三行左右的代码),那么如果您通过"匿名方法"注册事件处理逻辑,则您的实现可能会更容易阅读.(.NET 2.0+: Alternatively, if the event handling method is very brief (3 or so lines of code), your implementation may be easier to read if you register the event handling logic via an “anonymous method.")
步骤5:从事件中注销订户(事件处理方法).(Step 5: Unregister the subscriber (event handling method) from the event.)
- 当订阅服务器不再应从发布服务器接收事件通知时,则可以从事件中注销订阅服务器.(When the subscriber should no longer receive event notifications from the publisher, then you can unregister the subscriber from the event.)
- 考虑到在处置该订户时自动从发布者取消注册该订户,因此该步骤可以视为是可选的.(This step can be considered as optional, given that subscribers are automatically unregistered from publishers when the subscriber is disposed.)
13.示例事件实现(13. Sample Event Implementation)
本示例使用自定义事件处理程序,在自定义中携带事件数据(This example makes use of a custom event handler, carries event data in a custom) EventArgs
子类,使用类似字段的语法声明事件,否则符合推荐的事件实现标准和准则. .NET 2.0+功能(匿名方法等)均未使用,以使显示尽可能清晰.(subclass, declares an event with field-like syntax, and otherwise conforms to recommended event implementation standards and guidelines. None of the .NET 2.0+ features (anonymous methods, etc) are used in order to keep the presentation as explicit as possible.)
通过"文件移动器"实用程序移动文件时(在示例项目中),将引发此示例事件.事件数据包括(1)移动文件的名称,(2)源文件夹路径和(3)目标文件路径.(This sample event is raised when a file is moved by a “file mover” utility (in the sample project). The event data includes (1) the name of the file moved, (2) the source folder path, and (3) the destination file path.)
13.1示例事件发布者代码(13.1 Sample Event Publisher Code)
步骤1:子类EventArgs(Step 1: Subclass EventArgs)
在这里,我们派生一个新类,(Here we derive a new class,) MoveFileEventArgs
,来自(, from) EventArgs
为了封装发送给订户的事件数据.(in order encapsulate the event data sent to the subscriber.)
public class MoveFileEventArgs : EventArgs
{
// Fields
private string m_FileName = string.Empty;
private string m_SourceFolder = string.Empty;
private string m_DestinationFolder = string.Empty;
// Constructor
public MoveFileEventArgs(string fileName, string sourceFolder,
string destinationFolder)
{
m_FileName = fileName;
m_SourceFolder = sourceFolder;
m_DestinationFolder = destinationFolder;
}
// Properties (read-only)
public string FileName
{
get { return m_FileName; }
}
public string SourceFolder
{
get { return m_SourceFolder; }
}
public string DestinationFolder
{
get { return m_DestinationFolder; }
}
}
步骤2:事件处理程序(委托)(Step 2: Event Handler (delegate))
在这里,我们声明一个符合事件约定的新委托,返回(Here we declare a new delegate that conforms to the event conventions, returns) void
并接受两个参数;一个(and accepts two parameters; an) object
名为”(named ‘) sender
和(’ and) EventArgs
名为"(named ‘) `` ‘.(’.)
public delegate void MoveFileEventHandler(object sender,
MoveFileEventArgs e);
步骤3:声明活动(Step 3: Declare the Event)
在这里,我们使用类似字段的语法声明事件.(Here we declare the event using the field-like syntax.)
public event MoveFileEventHandler MoveFile;
步骤4:事件引发方法(Step 4: Event-Raising Method)
在这里,我们声明引发事件的方法.(Here we declare the method that raises the event.)
private void OnMoveFile()
{
if (MoveFile != null) // will be null if no subscribers
{
MoveFile(this, new MoveFileEventArgs("SomeFileName.txt",
@"C:\TempSource", @"C:\TempDestination"));
}
}
步骤5:引发活动(Step 5: Raise the Event)
在这里,我们有一种方法可以完成您感兴趣的工作(移动文件).工作完成后,将引发事件的方法被调用.(Here we have a method that would do the work of interest (move the file). Once the work is completed, the method that raises the event is called.)
public void UserInitiatesFileMove()
{
// code here moves the file.
// Then we call the method that raises the MoveFile event
OnMoveFile();
}
13.2事件订阅者代码示例(13.2 Sample Event Subscriber Code)
步骤1:编写事件处理方法(Step 1: Write the Event Handling Method)
当(This method is called when the) fileMover
实例引发(instance raises the) MoveFile
事件.它的签名与事件处理程序的签名完全匹配.(event. It’s signature exactly matches the event handler’s signature.)
void fileMover_MoveFile(object sender, MoveFileEventArgs e)
{
MessageBox.Show(sender.ToString() + " moved the file, " +
e.FileName + ", from " +
e.SourceFolder + " to " + e.DestinationFolder);
}
步骤2:实例化事件发布者(Step 2: Instantiate the Event Publisher)
FileMover fileMover = new FileMover();
步骤3:在我们向事件注册订户(事件处理方法)时实例化事件处理程序(Step 3: Instantiate the event handler as we register the subscriber (event handling method) with the event)
这种方法结合了14.2节中列出的步骤中的步骤3和4.(This approach combines steps 3 and 4 from the steps listed in the section 14.2.)
fileMover.MoveFile += new FileMover.MoveFileEventHandler(
fileMover_MoveFile);
14.处理.NET Framework组件引发的事件-演练和示例(14. Handling Events Raised by .NET Framework Components - Walkthrough and Example)
在.NET Framework自己的事件实现中可以找到本文描述的事件实现约定.本演练的目的是演示一个.NET Framework组件如何公开其事件,以及如何编写引发事件时运行的代码.您将看到所需的步骤只是实现您自己的事件和事件处理方法所需的建议步骤的一部分.(The event implementation conventions described in this article can be found throughout the .NET Framework’s own event implementations. The purpose of this walkthrough is to show how one .NET Framework component exposes its events, and how you can write code that runs when the event is raised. You will see that the steps required are simply a subset of the recommended steps required to implement your own events and event handling methods.)
本节指出了框架如何引导您使用这些约定.(This section points out how the Framework makes use of these conventions by walking you through the) FileSystemWatcher
组件(component’s) Deleted
事件实施.的(event implementation. The) FileSystemWatcher
是.NET Framework中提供的类(is a class provided in the .NET Framework’s) System.IO
命名空间.当特定磁盘IO活动在特定文件夹中发生时(例如,创建新文件,修改或删除文件等),可以使用此类来通知您的应用程序.(namespace. This class can be used to notify your application when specific disk IO activity takes place in a specific folder (e.g., a new file is created, a file is modified, or deleted, etc).)
14.1文件SystemWatcher"已删除"事件实现(14.1 File SystemWatcher ‘Deleted’ Event Implementation)
的(The) System.IO.FileSystemWatcher
类公开了(class exposes the) Deleted
事件-由(event — which is raised by a) FileSystemWatcher
它检测到监视文件夹中的文件已删除时的实例.(instance when it detects that a file has been deleted in a watched folder.)
活动声明(Event Declaration)
.NET Framework的FileSystemWatcher类是这样声明其Deleted事件的:(This is how the .NET Framework’s FileSystemWatcher class declares its Deleted event:)
public event FileSystemEventHandler Deleted
代表宣言(Delegate Declaration)
使用的代表是(The delegate used is) FileSystemEventHandler
在.NET Framework中声明为:(, which is declared in the .NET Framework as:)
public delegate void FileSystemEventHandler (Object sender,
FileSystemEventArgs e)
请注意(Notice that the) FileSystemEventHandler
委托符合接受两个参数的约定-第一个命名(delegate conforms to the convention of accepting two parameters — the first named) sender
是类型(is of type) System.Object
,第二个参数名为(, and the second parameter is named ‘) `` ‘并且类型(’ and is of type) System.EventArgs
或其后代.在这种情况下,(or a descendent thereof. In this case, the) FileSystemEventArgs
指定的是后代(如下所述).(specified is a descendent (as further described below).)
自定义EventArgs(Custom EventArgs)
的(The) Deleted
事件传达有关通过以下子类删除的文件或目录的信息:(event communicates information about the file or directory that was deleted through a subclass of) System.EventArgs
:(:)
public class FileSystemEventArgs : EventArgs {}
的(The) FileSystemEventArgs
类扩展(class extends) System.EventArgs
通过添加以下属性:(by adding the following properties:)
ChangedType
—获取发生的目录更改的类型(以(— Gets the type of directory change that occurred (communicated as a)WatcherChangeType
已创建,已删除,已更改等的枚举值)(enumeration value of Created, Deleted, Changed, etc.))FullPath
—获取到受影响的文件或目录的标准路径(— Gets the fully qualified path to the affected file or directory)Name
—获取受影响的文件或目录的名称(— Gets the name of the affected file or directory) 当…的时候(When the)Deleted
事件引发,(event is raised, the)FileSystemWatcher
实例将路径,文件名等发送给事件订阅者.这意味着每个订户不仅了解到文件已删除,而且特别了解到哪个文件被删除.(instance sends the path, file name, etc. to the event subscribers. This means that each subscriber not only learns that a file was deleted, but specifically which file was deleted.) 请注意,事件处理程序的名称(Notice that the name of the event handler,)FileSystemEventHandler
,并不完全符合建议事件处理程序名称应为的命名约定(, does not conform exactly to the naming convention that suggests the event handler name should be)事件的名称,后跟单词Handler(the name of the event followed by the word, Handler).请记住,约定不是法律或规则.相反,它们只是使您的代码更易于理解的建议.在这种情况下(. Remember, the conventions are not laws or rules. Instead, they are just suggestions for making your code more easily understood. In the case of the)FileSystemWatcher
类中,实现了一个事件处理程序来支持许多事件,包括(class, one event handler was implemented to support a number of events, including)Deleted
,(,)Created
和(and)Changed
-因此,从严格的命名约定解释中可以稍作改动.严格遵守该公约将导致创建3个代表,除了名称(例如,(— thus the minor break from a strict interpretation of the naming convention. A strict adherence to the convention would have resulted in the creation of 3 delegates that are identical except for the name (e.g.,)DeletedHandler
,(,)CreatedHandler
,等等.)选择的名称可能像(, etc.) Alternatively the name chosen could have been something like)DeletedOrCreatedOrChangedHandler
-简直太荒谬了.在这种情况下,[感谢!]选择了与约定的合理偏离.(— which would have been ridiculous. In this case a reasonable deviation from the convention was [thankfully!] chosen.)
14.2处理FileSystemWatcher.Deleted事件(14.2 Handling the FileSystemWatcher.Deleted Event)
鉴于.NET Framework提供了上述事件实现,以下是您可以在类中编写的代码示例,该代码将订阅(Given the above event implementation provided by the .NET Framework, the following is an example of code you could write in your class that would subscribe to the) Deleted
一个事件(event of a) FileSystemWatcher
实例.此代码将使您的应用程序能够响应在(instance. This code would enable your application to respond to files being deleted in the)**C:\温度(C:\Temp)**目录.(directory.)
// Import the namespace
using System.IO;
// Declare fsWatcher variable - likely at the class level.
FileSystemWatcher fsWatcher = new FileSystemWatcher();
//Initialize the instance - possibly in the constructor or method called from
constructor.
fsWatcher.Path = "C:\Temp";
fsWatcher.Deleted += new FileSystemEventHandler(fsWatcher_Deleted);
// this Boolean property enables or disables the raising of events by the
FileSystemWatcher instance.
fsWatcher.EnableRaisingEvents = true;
// include if updating Windows Forms components
fsWatcher.SynchronizingObject = this;
// event handling method
void fsWatcher_Deleted(object sender, FileSystemEventArgs e)
{
MessageBox.Show(e.Name + " was deleted" );
}
15. Windows窗体事件(15. Windows Forms Events)
Windows窗体实现自己的事件,并便于处理由窗体内或窗体类中的容器控件内的控件引发的事件.此外,Windows窗体类中事件的实现和处理需要仔细考虑,并且即使Windows窗体及其包含的控件都具有"线程相似性",也要采取独特的步骤,这意味着它们的属性只能通过在同一窗体中运行的代码来更新创建表单或控件的线程.(Windows Forms implement events of their own, as well as facilitate the handling of events raised by controls contained within the form or within container controls in the form class. Additionally, the implementation and handling of events in Windows Forms classes require careful consideration and even unique steps be taken given that Windows Forms and the controls they contain exhibit “thread affinity” — meaning that their properties can be updated only by code running in the same thread that created the Form or controls.) 这些和其他Windows窗体特定的注意事项在本节中介绍.(These and other Windows Forms-specific considerations are presented in this section.)
15.1 NET 1.x和2.0+的区别-局部类(15.1 NET 1.x and 2.0+ Difference - Partial Classes)
在.NET Framework 2.0版中引入了"部分类"的概念.偏类是用(The concept of “partial class” was introduced in the .NET Framework version 2.0. A partial class is a class declared with the) partial
关键字,并在两个或多个源代码文件中定义了部分类.编译器从包含部分类的所有文件中检索定义部分类的所有源代码,并输出一个[compiled]类.也就是说,部分类可能存在于两个或多个源代码文件中,但是当编译应用程序时,“类片段"将被编译为输出程序集中的一个类.(keyword, and with parts of the class defined in two or more source code files. The compiler retrieves all source code defining the partial class from all files containing the partial class, and outputs one [compiled] class. That is, a partial class may exist in two or more source code files, but when the application is compiled, the “class fragments” are compiled into one class in the output assembly.)
局部类的好处包括:(1)通过使用不同的源代码文件,多个开发人员可以同时处理同一类的不同部分; (2)自动代码生成工具可以写入一个源代码文件,而人类开发人员可以将其代码保存在单独的文件中,而不必担心他们的更改最终会被自动代码生成工具所覆盖.第二个好处是从Visual Studio 2005开始的Windows窗体项目中实现的.将新窗体添加到Windows窗体项目时,Visual Studio会自动将窗体创建为在两个源代码文件中定义的部分类.包含Visual Studio生成的代码的文件名为(The benefits of partial classes include (1) multiple developers can work on different parts of the same class at the same time, by working with different source code files; and (2) automated code generating tools can write to one source code file, while human developers can maintain their code in a separate file, without concern that their changes may eventually be overwritten by the automated code generating tool. This second benefit is realized in Windows Forms projects starting with Visual Studio 2005. When you add a new form to a Windows Forms project, Visual Studio automatically creates the form as a partial class that is defined in two source code files. The file that contains code generated by Visual studio is named) FormName.Designer.cs
,而用于开发人员代码的文件名为(, while the file intended for developer code is named)FormName.cs(FormName.cs).因此,例如,如果您有Visual Studio 2005创建一个名为MainForm的窗体,则将创建以下两个源代码文件:(. So, for example, if you have Visual Studio 2005 create a form named MainForm, then the following two source code files will be created:)
MainForm.cs(MainForm.cs)—包含部分类定义:(— contains the partial class definition:)
public partial class MainForm : Form
{
// developers write code here
}
MainForm.Designer.cs(MainForm.Designer.cs)—包含部分类定义:(— contains the partial class definition:)
partial class MainForm
{
// Windows Forms designer writes code here
}
当您使用Visual Studio Windows窗体设计器向窗体添加控件时,设计器会将必要的代码添加到窗体中.(When you add controls to the form by using the Visual Studio Windows Forms designer, the designer will add the necessary code to the)**FormName.Designer.cs(FormName.Designer.cs)**文件.(file.) 开发人员不应直接在(Developers should not directly modify the source code in the)**FormName.Designer.cs(FormName.Designer.cs)**文件,因为设计人员可能会覆盖此类更改.通常,所有开发人员代码都应使用(file because it is possible that the designer would overwrite such changes. As a general rule, all developer code should be written in the)**FormName.cs(FormName.cs)**文件.(file.) .NET 1.x根本没有部分类.所有源代码(无论是由Visual Studio还是由开发人员编写的)都放入单个源代码文件中.尽管Visual Studio Windows窗体设计器仅尝试在该文件的一个部分中编写代码,但是可能在同一部分中包含开发人员代码和生成的代码,而Windows窗体设计器可能会覆盖开发人员编写的代码.(.NET 1.x simply does not have partial classes. All source code — whether written by Visual Studio or a developer — goes into a single source code file. While the Visual Studio Windows Forms designer attempts to write code in just one section of that file, it is possible to have developer code and generated code in the same sections, with the possibility that the Windows Forms designer would overwrite developer-written code.)
15.2事件的部分类和Windows Forms Designer注意事项(15.2 Partial Classes and Windows Forms Designer Considerations For Events)
当Visual Studio 2005为您创建事件处理实现时,事件处理程序/注册代码将被写入到(When Visual Studio 2005 creates an event handling implementation for you, the event handler/registration code is written to the)**FormName.Designer.cs(FormName.Designer.cs)**文件,只有一个事件处理方法存根自动写入(file, with only an event handling method stub automatically written to the)**FormName.cs(FormName.cs)**文件.这种安排的目的是让Windows窗体设计器编写所有可以自动执行的事件相关代码(将事件处理方法与事件处理程序等连接在一起).设计人员无法为您创建的唯一部分是事件处理方法内部发生的特定编程逻辑.因此,当Visual Studio完成为您提供的所有功能后,您所拥有的就是(1)将所有与事件相关的"管道"代码隐藏在(file. The intent behind this arrangement is for the Windows Forms designer to write all of the event-related code that can be automated (wiring up the event handling method with the event handler, etc.). The only part that the designer cannot create for you is the particular programming logic that is to take place inside of the event handling method. So, when Visual Studio is finished doing everything it can for you, what you have is (1) all of the event-related “plumbing” code tucked away in the)**FormName.Designer.cs(FormName.Designer.cs)**文件; (2)中的事件处理方法存根在等您(file; with (2) an event handling method stub waiting for you in the)**FormName.cs(FormName.cs)**文件.完成事件处理实现所必须要做的就是在事件处理方法存根中编写必要的代码.(file. All that you must do to finish the event handling implementation is write the necessary code in the event handling method stub.)
15.3演练-处理Windows窗体事件(15.3 Walkthrough - Handling a Windows Forms Event)
以下步骤将引导您完成一个(The following steps walk you through the implementation of a) FormClosing
Windows窗体中名为MainForm的事件处理方法.(event handling method in a Windows Form named MainForm.)
- 使用Visual Studio .NET,创建一个新的Windows Forms项目,然后添加一个名为MainForm的新Form.(Using Visual Studio .NET, create a new Windows Forms project, and add a new Form named MainForm.)
- 在设计视图中打开MainForm的情况下,右键单击表单的裸露部分(而不是控件),然后从弹出菜单中选择"属性”.出现的"属性"对话框可以显示表单属性或事件.如果尚未选择,请单击属性对话框顶部工具栏中的"事件"按钮(带有闪电图标).(With MainForm opened in design view, right-click over an exposed portion of the form (not over a control), and select Properties from the popup menu. The Properties dialog that appears can display either form properties or events. If it is not already selected, click on the Events button (it has the lightening bolt icon) in to toolbar at the top of the properties dialog.)
- 在"事件"对话框中,找到您希望应用程序响应的事件.就我们而言,这是(In the Events dialog, locate the event for which you want your application to respond. In our case, this is the)
FormClosing
事件.双击行中任何地方(event. Double-click anywhere in the row where)FormClosing
被列出.(is listed.) 此时发生两件事.首先,Windows窗体设计器将以下行插入到(Two things happen at this point. First, the Windows Forms designer inserts the following line to the)**MainForm.Designer.cs(MainForm.Designer.cs)文件.*(*file.)
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(
this.MainForm_FormClosing);
其次,Windows Forms设计器将以下方法存根插入到(Second, the Windows Forms designer inserts the following method stub into to the)**MainForm.cs(MainForm.cs)文件.*(*file.)
private void MainForm_FormClosing(object sender,
FormClosingEventArgs e)
{
// your event handling code goes here
}
使用.NET 1.x(不具有部分类)时,会生成相同的代码,但会进入单个(* When using .NET 1.x (which does not have partial classes), the same code is generated, but goes into the single*)**MainForm.cs(*MainForm.cs*)**文件.(*file.*)
您会看到没有任何东西被隐藏.存在实现事件处理逻辑所需的所有代码,可供您查看并根据需要进行修改. Windows Forms设计器所做的全部工作就是以符合建议的事件标准的方式编写样板代码,并将该代码放在部分类文件中,然后您可以将其扩展以用于特定于应用程序的目的.(*You can see that nothing has been hidden from you. All code required to implement the event handling logic is present and available for you to review and possibly modify as necessary. All that the Windows Forms designer does is write the boilerplate code in a way that conforms to the recommended event standards, and places that code in the partial class files where you can then extend it for your application-specific purposes.*)
如果要更改Windows窗体设计器为您生成的事件处理方法的名称,则可以随意进行.只需确保在两个(*If you want to change the name of the event handling method that the Windows Forms designer generates for you, you are free to do so. Just be sure to change the method name in both the*)**MainForm.cs(*MainForm.cs*)**文件及其在事件处理程序中的注册位置(*file, and where it is registered with the event handler in*)**MainForm.Designer.cs(*MainForm.Designer.cs*)**.(*.*)
另外,(*On a separate note, the*) FormClosing
事件是可以取消的"事件之前".这意味着在关闭表单之前引发事件,并且事件处理过程可以取消事件,从而防止关闭表单.(*event is a “before event” that is cancellable. This means that the event is raised before the form is closed, and the event handling procedure can cancel the event, thereby preventing the form from being closed.*)
使该事件可取消的具体原因是(*What specifically makes this event cancellable is the*) FormClosingEventArgs
参数,它是扩展的类类型(*parameter, which is of a class type that extends*) System.ComponentModel.CancelEventArgs
.取消(*. To cancel the*) FormClosing
事件,则应设置布尔值(*event, you would set the Boolean*) Cancel
的财产(*property of*) FormClosingEventArgs
至(*to*) true
, 像这样:(*, like this:*)
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
e.Cancel = true; // prevents the form from closing
}
15.4 Windows窗体和线程注意事项(15.4 Windows Forms and Threading Considerations)
Windows窗体及其包含的控件只能通过在创建该控件的同一线程上运行的代码来调用其属性和方法(即Windows窗体和控件具有线程亲和力).因此,当来自非UI线程的代码试图执行修改UI组件的代码时,您可能会遇到意外的行为或运行时异常.(Windows forms and the controls they contain can have their properties and methods called only by code running on the same thread that created the control (i.e. Windows forms and controls exhibit thread affinity). You may, therefore, run into unexpected behavior or runtime exceptions when code from a non UI thread attempts to execute code that modifies UI components.)
请注意,即使您未在应用程序中显式或有意使用多个线程,也可能会遇到与此UI线程相似性相关的线程问题.例如,(Beware that you might run into threading issues related to this UI thread affinity even if you are not explicitly or knowingly employing multiple threads in your application. For example, the) FileSystemWatcher
类出于自身目的自动产生一个额外的后台线程.因此,如果您的代码使用了(class automatically spawns an additional background thread for its own purposes. Consequently, you may encounter threading issues if your code makes use of the) FileSystemWatcher
,以及任何相关的事件处理方法最终都会导致对UI控件的更新.另外,您正在使用的任何第三方组件都可能会产生其他您最初不知道的后台线程.(, and any of the related event handling methods ultimately cause an update to a UI control. Separately, any 3rd party components you are using may spawn additional background threads of which you are initially unaware.)
有几种方法可以减轻这些线程问题:(There are a few ways to mitigate these threading issues:)
- 设置(Set the)
SynchronizingObject
财产(在可用和相关的地方)(property (where available and relevant)) - 利用(Make use of)
Control.InvokeRequired
和(and)Control.Invoke()
并调用更新UI的代码.(and to call the code that updates the UI.) - 组件开发人员可以利用(Component developers can make use of the)
SynchronizationContext
,(,)AsyncOperatio
n和(n, and)AsyncOperationManager
类.(classes.)
同步对象(SynchronizingObject)
某些.NET组件提供了(Some .NET components provide a) SynchronizingObject
属性.这些组件的示例包括(property. Examples of these components include) FileSystemWatcher
,(,) Timer
和(, and the) Process
类.设置(classes. Setting the) SynchronizingObject
使事件处理方法可以在创建要更新的UI组件的同一线程上调用.因此,例如,计时器的(enables event handling methods to be called on the same thread that created the UI component that is to be updated. So, for example, a Timer’s) Elapsed
从线程池线程引发事件.当计时器组件的(event is raised from a thread pool thread. When the Timer component’s) SynchronizingObject
设置为UI组件,则事件的处理方法(is set to a UI component, the event handling method for the) Elapsed
在运行UI组件的同一线程上调用event.然后可以从(event is called on the same thread on which the UI component is running. The UI component can then be updated from the) Elapsed
事件处理方法.(event handling method.)
请注意,Visual Studio可能会自动设置(It should be noted that Visual Studio may automatically set the) SynchronizingObject
包含组件的控件的属性.因此,您可能永远不会需要显式设置(property to the control that contains the component. Consequently, you may never encounter the need to explicitly set the) SynchronizingObject
属性.(property.)
但是,在某些情况下,(There are scenarios, however, where the) SynchronizingObject
属性可能需要显式设置.例如,当您有一个在Windows窗体中实例化的类库时,该类库包含一个(property may need to be set explicitly. For example, when you have a class library that is instantiated within a Windows Form, and that class library contains a) FileSystemWatcher
实例.的(instance. The) FileSystemWatcher
产生一个额外的后台线程,从该线程引发事件.这些事件将在类库中处理.到现在为止还挺好.该类库可以处理事件,因为它缺少Windows Forms控件中的线程关联性.该类库可以响应于接收到一个(spawns an additional background thread from which its events are raised. These events would then be handled within the class library. So far so good. The class library can handle the events because it lacks the thread affinity found with Windows Forms controls. The class library may, in response to receiving a) FileSystemWatcher
事件,引发一个新事件,然后在包含Windows窗体实例中处理该事件.如果(event, raise a new event that is then handled in the containing Windows Form instance. The following exception will occur if the) SynchronizingObject
尚未设置为该窗体(或窗体上的相关控件),或者未通过调用UI更新代码(has not been set to that Form (or relevant control on it), or the UI updating code is not invoked via) Control.Invoke()
,如下所述.(, as described next.)
System.InvalidOperationException was unhandled:
Cross-thread operation not valid: Control 'ControlNameHere' accessed from a
thread other than the thread it was created on.
Control.Invoke()和InvokeRequired(Control.Invoke() and InvokeRequired)
该规则有两个相关的例外规定:(There are two relevant exceptions to the rule that states that)Windows窗体控件无法从创建它们的线程之外的其他线程访问(Windows Forms controls cannot be accessed from a thread other than the thread on which they were created).每个控件都继承(. Every control inherits the) Invoke()
方法和(method and) InvokeRequired
属性,可以从其他线程访问.(property, which can be accessed from other threads.) Invoke()
接受单个参数,该参数的类型为委托.接到电话时(takes a single argument, which is of type delegate. When called,) Invoke()
使委托调用向其注册的任何方法.显然,任何通过(causes the delegate to call any methods registered with it. Obviously then, any code called via) Invoke()
将在控件所在的同一线程上执行.因此,要从另一个线程中的代码更新在一个线程上运行的UI控件,只需(1)将将UI控件更新为自己的方法的代码;然后(2)向该委托注册该方法,然后将该委托(3)传递给UI控件的(will be executed on the same thread in which the control exists. So, to update UI controls running on one thread from code in another thread, simply (1) break out the code that updates the UI control into its own method; then (2) register that method with the delegate that is then (3) passed to the UI control’s) Invoke()
方法.(method.)
InvokeRequired
当前代码在创建控件的线程以外的线程上运行时,返回true.您可以查询的值(returns true when the current code is running on a thread other than the thread on which the Control was created. You can query the value of) InvokeRequired
以确定您的代码是否可以直接更新控件,或者是否必须通过(to determine whether your code can directly update the Control, or if such updates must be routed through the) Invoke()
方法.(method.)
SynchronizationContext,AsyncOperation和AsyncOperationManager(SynchronizationContext, AsyncOperation, and AsyncOperationManager)
这些类是.NET Framework 2.0版的新增功能,为组件的开发人员提供了另一个选项,这些组件可以异步引发事件以解决上述线程问题.使用的重要好处(New to the 2.0 version of the .NET Framework, these classes provide another option for developers of components that raise events asynchronously to address the threading issues described above. An important benefit of using) System.ComponentModel.AsyncOperation
是它为事件发布者(组件)中的线程问题(如上所示)提供了解决方案,而上述两种选择((is that it provides the solution to the threading issues (presented above) in the event publisher (component), whereas the two alternatives presented above () Control.Invoke
和(and) SynchronizingObject
)的解决方案与订户.() place the solution with the subscribers.)
16.可取消的事件(16. Cancellable Events)
可取消事件通常由组件执行,该组件将执行一些可以取消或以其他方式阻止发生的动作. Windows Form类的(Cancellable events are generally raised by a component that is about to perform some action that can be cancelled or otherwise prevented from happening. The Windows Form class’s) FormClosing
事件是可取消事件的示例.您要防止表单关闭的典型情况是用户尚未保存更改.在这种情况下,(event is an example of a cancellable event. A typical scenario in which you’d want to prevent a form from closing would be where the user has not saved changes. In this scenario, your) FormClosing
事件处理方法可以实现检测未保存更改的存在的逻辑.如果有,逻辑可以提示用户保存其更改.如果用户选择保存更改,则您的逻辑将取消(event handling method could implement logic that detects the presence of unsaved changes. If there are any, the logic could prompt the user to save their changes. If the user elects to save their changes, your logic would cancel the) FormClosing
事件.这将防止表单关闭,从而使用户有机会在尝试再次关闭表单之前查看其更改并可能保存更改.(event. This would prevent the form from closing, thereby giving the user the opportunity to review their changes and possibly save them before attempting to close the form again.)
可取消事件的内部运作可能非常简单.考虑到事件经常向订户发出信号,通知状态或某些其他活动即将"即将发生",因此此"事件前"是事件发布者确定其(发布者)是否应允许更改的理想机会状态(或活动)发生.如果允许进行活动(即,没有任何内容告诉发布者中止操作),则发布者允许状态发生更改,随后/可选地引发事件后.(The inner workings of a cancellable event can be quite straight-forward. Considering that events frequently signal to subscribers that a change in state or some other activity is “about to” place, this “pre-event” represents an ideal opportunity for the event publisher to determine if it (the publisher) should allow for the change in state (or activity) to take place. If the activity is allowed to take place (i.e., nothing tells the publisher to abort the operation), the publisher then allows for the change in state to happen and subsequently/optionally raises the post-event.)
总之,可取消事件实际上是两个事件,并且在这些事件之间发生一些活动. “活动前"发生在活动之前.该活动然后发生(或不发生).如果活动发生,则通常会引发"后事件”.因此,确切地说,即使我们说我们有一个可取消的事件,也不会取消任何事件.相反,在两个事件之间发生的活动被取消了,并且很可能根本无法启动.(In sum, a cancellable event is really two events and some activity that takes place between those events. The “pre-event” happens before the activity. The activity then takes place (or not). If the activity takes place, then the “post-event” is typically raised. So, to be precise, no event is being cancelled even though we say we have a cancellable event. Rather, the activity that takes place between the two events is what is cancelled — and likely prevented from starting at all.)
为了支持上述可取消事件的概念,.NET Framework为我们提供了(In support of the above notion of cancellable event, the .NET Framework provides us with the) System.ComponentModel.CancelEventArgs
我们可以直接使用或扩展以用于特定于应用程序目的的类.(class that we can use directly or extend for our application-specific purposes.) CancelEventArgs
延伸(extends) System.EventArgs
通过提供布尔值(by providing the Boolean) Cancel
设置为(property that, when set to) true
由事件订阅者使用,由事件发布者用来取消事件.事件发布者代码创建了一个实例(by an event subscriber, is used by the event publisher to cancel the event. The event publisher code creates an instance of) CancelEventArgs
会在预赛开始时发送给订阅者.默认情况下,事件处理方法(在任何/所有订户中)都是同步运行的.因此,在所有事件处理方法都运行完毕之前,事件前发出的状态(或活动)变化不会发生.当然,事件发布者保留其对(that is sent to subscribers when the pre-event is raised. By default, event handling methods (in any/all subscribers) are run synchronously. Consequently, the change in state (or activity) signaled by the pre-event cannot happen until after all event handling methods have run to completion. Of course the event publisher retains its reference to the) CancelEventArgs
引发事件后的实例.因此,如果有任何事件处理方法设置了(instance after the event is raised. So, if any event handling methods set the) Cancel
财产(property to) true
,事件发布者会在尝试进行状态更改之前看到此消息,因此可以做出相应的响应.(, the event publisher will see this before attempting to proceed with the change in state, and can therefore respond accordingly.)
活动的顺序可能是这样的:(The sequence of activity could be something like this:)
- 事件发布者实例化(the event publisher instantiates)
System.ComponentModel.CancelEventArgs
(或其子类),名称为"((or subclass thereof) with the name ‘) `` ‘(') - 然后,事件引发方法引发事件,并传递"(the event raising method then raises the event, passing ‘) `` ‘到事件订阅者(默认(’ to the event subscribers (defaulting)
Cancel
至(to)false
)()) - 然后,事件处理方法(当然是在事件订阅者中)设置(the event handling method (in the event subscriber, of course) then sets the value of)
e.Cancel
至(to)true
,可能是通过提示用户(, possibly by prompting the user) - 然后,事件引发方法将获得(the event raising method then gets the value of)
e.Cancel
,并作出相应的回应.在这种情况下(, and responds accordingly. In the case where)e.Cancel = true
,则逻辑将阻止状态或活动的更改发生(例如,表单关闭).(, the logic would then prevent the change in state or activity from taking place (e.g., form closing).) 如果事件有多个订阅者,则如果设置了任何事件处理方法,则事件将被取消(In the case where the event has multiple subscribers, the event will be cancelled if any of the event handling methods set)e.Cancel = true
.更具体地说,事件发布者将看到(. More specifically, the event publisher will see that)e.Cancel = true
当最后一个事件处理方法返回时(它们被同步调用).(when the last of the event handling methods returns (they are called synchronously).) 归根结底,这一切(At the end of the day, all that)CancelEventArgs
为我们做的是为事件订阅者提供一种沟通机制(does for us is provide a mechanism for an event subscriber to communicate a)true
|(|)false
对事件发布者的价值. “取消事件"的实际工作和含义完全由您决定,因为您将编写对…的值做出响应的逻辑(value to the event publisher. The actual work and meaning of “cancelling the event” is entirely up to you, as you would write the logic that responds to the value of)e.Cancel
.(.)
取消长时间运行的操作(Cancelling a Long-Running Operation)
一旦活动开始,上述事件取消方案就不会提供任何机制来阻止某些活动(或状态更改)的发生.这是因为事件发布活动都同步(或顺序)发生.可以使用事前事件来阻止活动的开始,但是一旦活动开始,它便会运行完成,因为订阅者的事前事件处理方法已经完成,因此无法再与事件发布者进行通信.(The event cancellation scenario described above offers no mechanism for stopping some activity (or change in state) from occurring once the activity has begun. This is because the event publishing activities all happen synchronously (or sequentially). The pre-event can be used to prevent the activity from starting, but once it has started, it will run to completion because the subscriber’s pre-event event handling methods have run to completion and can therefore no longer communicate with the event publisher.)
如果您需要使订阅者在操作开始后(例如,在引发并处理预事件之后)取消操作,则上述基本事件发布机制将不够用.您将需要采用一种更强大的事件发布机制,在该机制中,事件发布者可以异步执行其活动(例如,在后台线程上).基本思想是,客户端代码(在订户/观察者中)将请求事件发布者中发生的某些活动.然后,事件发布者将在后台线程上启动其活动.在后台任务运行时,客户端代码可以自由进行其他工作,也许可以处理来自用户的手势-这可能包括取消正在进行的异步操作的请求.发布者将需要实施逻辑,该逻辑将定期检查以查看客户端是否已请求取消,如果是,则停止其工作.(If you need to enable subscribers to cancel an operation after it has begun (e.g., after the pre-event has been raised and handled), the basic event publishing mechanism described above will not be sufficient. You would need to employ a more robust event publishing mechanism in which the event publisher conducts its activity asynchronously (e.g., on a background thread). The basic idea is that the client code (in the subscriber/observer) would request some activity to take place in the event publisher. The event publisher would then initiate its activity on a background thread. While the background task is running, the client code is free to proceed with other work, perhaps processing gestures from the user — which could include a request to cancel the asynchronous operation that is in progress. The publisher would need to implement logic that periodically checks to see if the client has requested cancellation and, if so, stop doing its work.)
开始异步处理的一种相对简单安全的方法是熟悉异步处理.(A relatively easy and safe way to get started with asynchronous processing is to become familiar with the) System.ComponentModel.BackgroundWorker
零件.的(component. The) BackgroundWorker
组件使您可以异步运行任务,报告任务进度(完成百分比),在任务启动后取消任务以及报告任务完成(带有返回值).进一步介绍异步处理模型和多线程替代方法以及相关问题超出了本文的范围.(component enables you to run a task asynchronously, report progress of the task (percentage towards completion), cancel the task after it has started, and report task completion (with return value). It is beyond the scope of this article to further present asynchronous processing models and multithreading alternatives and related issues.)
17. ASP.NET Web窗体事件(17. ASP.NET Web Forms Events)
在ASP.NET Web应用程序中,事件,事件处理程序和事件处理方法的创建所涉及的核心概念从根本上没有什么独特之处.本文中介绍的有关创建自定义事件和事件处理程序的所有内容,都适用于ASP.NET Web应用程序,适用于Windows Forms应用程序和C#代码库. ASP.NET Web应用程序的根本不同之处在于定义,引发和处理事件的上下文. HTTP的无状态性质及其请求/响应模型,ASP.NET HTTP请求管道的角色,ViewState的角色等都可以发挥作用-并对事件的引发和处理产生影响.除了事件基本原理外,如本文所述,还有特定于ASP.NET的事件相关概念,例如客户端事件(以ECMA Script,VBScript或JavaScript编写)回发和事件冒泡.(There is nothing fundamentally unique about the core concepts involved in the creation of events, event handlers, and event handling methods in ASP.NET Web applications. Everything presented in this article about creating custom events and event handlers applies equally well to ASP.NET Web applications as it does to Windows Forms applications and C# code libraries. What is fundamentally different about ASP.NET Web applications is the context in which events are defined, raised, and handled. The stateless nature of HTTP and its request/response model, the role of the ASP.NET HTTP request pipeline, the role of ViewState, etc. all come into play — and with implications for the raising and handling of events. Beyond event fundamentals, as presented in this article, there are ASP.NET-specific event-related concepts such as client-side events (written in ECMA Script, VBScript, or JavaScript) postbacks, and event bubbling.) 本文不会尝试解决ASP.NET Web应用程序上下文中的事件,因为合理的处理将使本文的长度增加一倍以上(并且本文已经足够长!).但是,应该注意的是,本文中介绍的基础知识将为新手Web应用程序开发人员提供坚实的基础.(This article does not attempt to address events in the context of ASP.NET Web applications because a reasonable treatment would more than double the length of this article (and this article is already long enough!). It should be noted, however, that the fundamentals presented in this article will provide the beginning Web application developer with a solid foundation upon which to build.)
18.资料来源(18. Sources)
图书(Books)
- 框架设计指南-可重用.NET库的约定,惯用语和模式,作者Krzysztof Cwalina和Brad Abrams,2006年.(Framework Design Guidelines - Conventions, Idioms, and Patterns for Reusable .NET Libraries, by Krzysztof Cwalina and Brad Abrams, 2006.)
- 通过C#进行CLR,第二版,Jeffrey Richter,2006年.(CLR via C#, 2nd Edition, by Jeffrey Richter, 2006.)
- Jon Skeet撰写的C#Depth.请点击(C# In Depth, by Jon Skeet. Click) 这里(here) 以获得早期访问版本信息.(for early access edition information.)
- Pro C#2005和.NET 2.0平台,Andrew Troelsen,2005年.(Pro C# 2005 and the .NET 2.0 Platform, by Andrew Troelsen, 2005.)
- 编程C#,第4版,作者Jesse Liberty,2005年.(Programming C#, 4th edition, by Jesse Liberty, 2005.)
- 编程.NET组件,第二版,Juval Lowy,2005年.(Programming .NET Components, 2nd Edition, by Juval Lowy, 2005.)
- 设计模式-可重用的面向对象软件的元素,作者:Erich Gamma,Richard Helm,Ralph Johnson和John Vlissides,1995年.(Design Patterns - Elements of Reusable Object-Oriented Software, by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, 1995.)
在线文章(Online Articles)
- Doug Purdy和Jeffrey Richter(MSDN)的"探究观察者设计模式”:(Exploring the Observer Design Pattern, by Doug Purdy and Jeffrey Richter (MSDN):) http://msdn2.microsoft. com/zh-CN/library/ms954621.aspx(http://msdn2.microsoft. com/en-us/library/ms954621.aspx)
- 在.NET中实现观察者(MSDN文章):(Implementing Observer in .NET (MSDN Article):) http://msdn2.microsoft. com/zh-CN/library/ms998543.aspx(http://msdn2.microsoft. com/en-us/library/ms998543.aspx)
- 委托类(MSDN文档):(Delegate Class (MSDN Documentation):) http://msdn2.microsoft.com/en-us/library/system.delegate.aspx(http://msdn2.microsoft.com/en-us/library/system.delegate.aspx)
- 代表和活动,乔恩`斯凯特(Jon Skeet):(Delegates and Events, by Jon Skeet:) www.yoda.arachsys.com/csharp/e vents.html(www.yoda.arachsys.com/csharp/e vents.html)
- Juval Lowy撰写的C#编码标准-准则和最佳做法,版本2.21,2007年.(C# Coding Standard - Guidelines and Best Practices, version 2.21, by Juval Lowy, 2007. Available at) www.idesign.net(www.idesign.net)
历史(History)
- 1.0版-2007年9月13日(Version 1.0 - September 13, 2007)
- 2.0版-2007年10月22日-添加了有关可取消事件的新部分,并扩展了1.x与2.0+差异的表示形式.(Version 2.0 - October 22, 2007 - added new section on cancellable events and expanded the presentation of 1.x vs. 2.0+ differences.)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
C# .NET Windows Visual-Studio VS2005 Dev 新闻 翻译