Professional Documents
Culture Documents
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. 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
1. Assumptions About the Audience 2. Terminology and Definitions 3. Delegates o 3.1 Definition and Usage of Delegates o 3.2 Why Delegates? o 3.3 Delegate Internals o 3.4 Delegates Are All The Same (there are no fundamentally differing types of delegates) 4. The Relationship Between Delegates and Events o 4.1 Event Handlers (in general) o 4.2 The Non Generic System.EventHandler Delegate o 4.3 The Generic System.EventHandler<TEventArgs> Delegate 5. Event Arguments (EventArgs) o 5.1 The Role of System.EventArgs o 5.2 Extending System.EventArgs o 5.3 The role of System.ComponentModel.CancelEventArgs 6. Event Declaration Syntax o 6.1 Event Declaration Syntax Alternatives o 6.2 Considerations For Choosing Between Field-like Syntax and Propertylike Syntax o 6.3 Publish/Subscribe mechanism Using Delegates Without Events (never do this) 7. Event Raising Code 8. Event Subscriber Registration and Unregistration o 8.1 Registering A Subscriber o 8.2 Unregistering A Subscriber 9. Event Handling Method 10. .NET 1.x vs. 2.0+ Considerations
10.1 Generics 10.2 Delegate Inference 10.3 Anonymous Methods 10.4 Partial Classes 11. Conventions o 11.1 Event Publisher Conventions o 11.2 Event Subscriber Conventions o 11.3 Naming Conventions 12. Steps to Creating Custom Events o 12.1 Prepare the Event Publisher o 12.2 Prepare the Event Subscriber 13. Sample Event Implementation o 13.1 Sample Event Publisher Code o 13.2 Sample Event Subscriber Code 14. Handling Events Raised by .NET Framework Components - Walkthrough and Example o 14.1 FileSystemWatcher 'Deleted' Event Implementation o 14.2 Handling the FileSystemWatcher.Deleted Event 15. Windows Forms Events o 15.1 NET 1.x and 2.0+ Difference - Partial Classes o 15.2 Partial Classes and Windows Forms Designer Considerations For Events o 15.3 Walkthrough - Handling a Windows Forms Event o 15.4 Windows Forms and Threading Considerations 16. Cancellable Events 17. ASP.NET Web Forms Events 18. Sources 19. History
o o o o
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. 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." 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.
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 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" 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 events and the GoF Observer pattern 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. Delegates
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.
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);
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 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);
... 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).
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 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.
... 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 is a special class in that compilers can derive from it, but you cannot derive from it explicitly. Your use of the C# delegate 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 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 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." 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; and (2) provide the same set of members, including the Invoke, BeginInvoke, and EndInvoke() methods, etc.
What differentiates delegate types is nothing more than: 1. The type name of the delegate. 2. The signature of the delegate including return type and number and types of parameters. 3. 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": 1. The type name: Predicate 2. The signature: returns bool, accepts a single object typed parameter for which the type, being generic, can be specified at design time. 3. 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, 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.
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).
Collapse
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:
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 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).
public
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 the custom delegate type defined in
event.
event keyword used to define the event. MyDelegate data type of the event (this is
Line 1.)
MyEvent
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 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.
Collapse
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;
Collapse
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: Collapse
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): Collapse
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.
2. 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 for reasons described in section 5.2. The first alternative listed above is strongly encouraged, and support for it is built into the .NET Framework through the System.EventArgs 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.
Collapse
string m_FileName = string.empty; // Constructor FileDeletedEventArgs(string fileName) { m_FileName = fileName; } // Property public string FileName { get { return m_FileName; } } }
Section 16 of this article presents cancellable events in greater detail (click here to go there now).
Collapse
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. Property-like syntax Collapse
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 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;
Collapse
The property-like syntax will be as thread safe as you make it. The following is a thread safe version: Collapse
private readonly object padLock = new object(); public event System.EventHandler<filedeletedeventargs />FileDeleted { add { lock (padLock) { FileDeleted += value; }
You can omit the lock{} blocks and padLock variable declaration if thread safety is of no concern.
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.
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); }
Collapse
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) }
Collapse
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
Collapse
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)] 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: Collapse
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. } } } }
Collapse
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.
thePublisher
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.
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.
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.1 Generics
In addition to the Generic-specific features presented elsewhere in this article (e.g., System.EventHandler<T>), 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.
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. Collapse
thePublisher.EventName += EventHandlingMethodName;
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. 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 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.
Collapse
The above logic can be rewritten with an anonymous method, like this: Collapse
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. 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: Collapse
// 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"); }
11. Conventions
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).
Choose a name that clearly communicates the state change the event represents. 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.
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, 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 subclass should be the name of the event, with 'EventArgs' appended
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
If you are using .NET Framework 1.x, then you should use the built-in System.EventHandler delegate 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 If you create your own delegate, then the delegate name should be comprised of the event name, with the word, 'Handler' appended
Event Handler (delegate) Signature As stated above, under "Delegate Name," you should use one of the System.EventHandler 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 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 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 'e' 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.
Collapse
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.
Collapse
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.
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
The signature of the event handling method must exactly match the delegate signature. According to the event handling conventions, as well as the EventHandler delegates provided by the .NET Framework, the event handling method must return void, while accepting exactly two parameters: an objecttyped variable named sender, and an EventArgs (or derived class) instance named 'e'.
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 }
Collapse
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: Collapse
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: Collapse
m_MailMonitor.MailArrived -= new EventHandler( this.MailMonitor_MailArrived);
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 cased examples: MailArrivedEventHandler, AppClosing, MyClassName
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 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.
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 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
If using .NET 1.x, consider using the built-in System.EventHandler delegate. If using .NET 2.0, consider using the built-in generic System.EventHandler<TEventArgs> delegate.
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.
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.
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.
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).
Declare class-level member variable that references the class or object that publishes the event of interest.
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. 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
Step 4: Register the subscriber (event handling method) with the event.
Any .NET version: Use the += syntax to register the event handler with the event. .NET 2.0+: Alternatively, through delegate inference, you can simply assign the method name directly to the event. .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."
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.
Collapse
// Properties (read-only) public string FileName { get { return m_FileName; } } public string SourceFolder { get { return m_SourceFolder; } } public string DestinationFolder { get { return m_DestinationFolder; } } }
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 'e'.
public delegate void MoveFileEventHandler(object sender, MoveFileEventArgs e);
Collapse
Step 3: Declare the Event Here we declare the event using the field-like syntax.
public event MoveFileEventHandler MoveFile;
Collapse
Step 4: Event-Raising Method Here we declare the method that raises the event. Collapse
private void OnMoveFile() { if (MoveFile != null) // will be null if no subscribers { } } MoveFile(this, new MoveFileEventArgs("SomeFileName.txt", @"C:\TempSource", @"C:\TempDestination"));
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.
Collapse
public void UserInitiatesFileMove() { // code here moves the file. // Then we call the method that raises the MoveFile event OnMoveFile(); }
Step 3: Instantiate the event handler as we register the subscriber (event handling method) with the event This approach combines steps 3 and 4 from the steps listed in the section 14.2.
fileMover.MoveFile += new FileMover.MoveFileEventHandler( fileMover_MoveFile);
Collapse
14. Handling Events Raised by .NET Framework Components - Walkthrough and Example
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 is a class provided in the .NET Framework's System.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).
Collapse
Delegate Declaration The delegate used is FileSystemEventHandler, which is declared in the .NET Framework as: Collapse
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 'e' and is of type System.EventArgs or a descendent thereof. In this case, the FileSystemEventArgs specified is a descendent (as further described below). Custom EventArgs The Deleted event communicates information about the file or directory that was deleted through a subclass of System.EventArgs: Collapse
public class FileSystemEventArgs : EventArgs {}
Name
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 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 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.
// event handling method void fsWatcher_Deleted(object sender, FileSystemEventArgs e) { MessageBox.Show(e.Name + " was deleted" ); }
Collapse
Collapse
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 file. Developers should not directly modify the source code in the 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 file. .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 Partial Classes and Windows Forms Designer Considerations For Events
When Visual Studio 2005 creates an event handling implementation for you, the event handler/registration code is written to the FormName.Designer.cs file, with only an event handling method stub automatically written to the FormName.cs 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 file; with (2) an event handling method stub waiting for you in the 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.
not already selected, click on the Events button (it has the lightening bolt icon) in to toolbar at the top of the properties dialog. 3. 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. Two things happen at this point. First, the Windows Forms designer inserts the following line to the MainForm.Designer.cs file.* Collapse
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler( this.MainForm_FormClosing);
Second, the Windows Forms designer inserts the following method stub into to the MainForm.cs file.* Collapse
private void MainForm_FormClosing(object sender, FormClosingEventArgs e) { // your event handling code goes here }
* When using .NET 1.x (which does not have partial classes), the same code is generated, but goes into the single MainForm.cs file. 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. 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 file, and where it is registered with the event handler in 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: Collapse
private void MainForm_FormClosing(object sender, FormClosingEventArgs e) { e.Cancel = true; // prevents the form from closing
Set the SynchronizingObject property (where available and relevant) Make use of Control.InvokeRequired and Control.Invoke() and to call the code that updates the UI. Component developers can make use of the SynchronizationContext, AsyncOperation, and AsyncOperationManager classes.
SynchronizingObject Some .NET components provide a SynchronizingObject property. Examples of these components include FileSystemWatcher, Timer, and the Process classes. Setting the SynchronizingObject 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 is set to a UI component, the event handling method for the Elapsed 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. 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 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 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 event, raise a new event that is then handled in the containing Windows Form instance. The following exception will occur if the SynchronizingObject 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. Collapse
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() and InvokeRequired There are two relevant exceptions to the rule that states that 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() 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. 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.
InvokeRequired
SynchronizationContext, AsyncOperation, and AsyncOperationManager 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.
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. 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 preevent 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: 1. the event publisher instantiates System.ComponentModel.CancelEventArgs (or subclass thereof) with the name 'e' 2. the event raising method then raises the event, passing 'e' to the event subscribers (defaulting Cancel to false) 3. the event handling method (in the event subscriber, of course) then sets the value of e.Cancel to true, possibly by prompting the user 4. 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.
there are ASP.NET-specific event-related concepts such as client-side events (written in ECMA Script, VBScript, or JavaScript) postbacks, and event bubbling. 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.