|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionEverything 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 Contents
1. Assumptions About the AudienceThis 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. Terminology and DefinitionsThe 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 stateThe 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, subjectThese 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, observerThese 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 notificationEvent 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, DelegatesA 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 patternEvents, 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. DelegatesIn order to understand events, as implemented in .NET applications, one must have a clear understanding of the .NET 3.1 Definition and Usage of DelegatesDelegates 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 ( private void MyMethod(string someString)
{
// method body here.
}
The following methods, however, cannot be referenced by a 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 Delegates and Overloaded MethodsIn 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 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 // 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 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 IndirectionA 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. 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 InvocationAll 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 FoundationAs 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 UsesDelegates 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 Delegate InternalsDeclaring a Delegate Results in A New Class Being CreatedA 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 The new class created from your delegate declaration can be understood as being a completed and full-blown 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 It should be noted that Meaning of MulticastThe meaning of "multicast" in Delegates are ImmutableDelegate 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 PointersFinally, 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: 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 What differentiates delegate types is nothing more than:
Take, for example, the generic Predicate delegate (
Beyond the type name, signature, and intended usage, the To be clear, it is not the case that the 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 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. The Relationship Between Delegates and EventsEvents 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 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. 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 HandlersYou can define your own event handlers (delegates), or you can use one of the event handlers provided by the .NET Framework (i.e., Consider the following: Line 1: public delegate void MyDelegate(string whatHappened);
Line 2: public event MyDelegate MyEvent;
Line 1 declares a delegate type for which any method can be assigned — provided that the method returns
Line 2 declares an event in terms of the delegate type. Notice that the event (which is named
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 HandlersWhile you can create your own event handlers (and sometimes you might need to), you should use one of the 4.2 The Non Generic System.EventHandler DelegateAvailable in version 1.x of the .NET Framework, the non generic This is how the .NET Framework declares the public delegate void EventHandler(object sender, EventArgs e);
4.3 The Generic System.EventHandler<TEventArgs> DelegateAvailable since the 2.0 version of the .NET Framework, the generic The declaration of this built-in delegate enforces the constraint that the type, 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 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 public event MyGenericEventHandler<MyPublisher, MyEventArgs> MyEvent;
The intent here would be that this event will only be raised by objects of type 5. Event Arguments (EventArgs)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 The Role of System.EventArgsYou have two basic alternatives for including event arguments with your events.
The first alternative listed above is strongly encouraged, and support for it is built into the .NET Framework through the Some events carry no data. In these cases, 5.2 Extending System.EventArgsThe existence of To illustrate the tradeoffs involved in sending a Here is an example of an 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 The Role of System.ComponentModel.CancelEventArgs
Section 16 of this article presents cancellable events in greater detail (click here to go there now). 6. Event Declaration Syntax6.1 Event Declaration Syntax AlternativesThe
1. Field-like syntaxpublic 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 2. Property-like syntaxpublic 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 Threading ConsiderationsThe 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 6. 2 Considerations For Choosing Between Field-like Syntax and Property-like SyntaxWhen 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 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. 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 7. Event Raising CodeFor 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 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 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 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 [ 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. Event Subscriber Registration and UnregistrationBy 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 Registering A SubscriberIn order to subscribe to an event, the subscriber needs three things:
The subscriber then registers its event handler (delegate) instance with the publisher, like this: thePublisher.EventName += new
MyEventHandlerDelegate(EventHandlingMethodName);
In the above line...
WARNING: Do not use the 8.2 Unregistering A SubscriberA subscriber can unregister from the publisher, like this: thePublisher.EventName -=
EventHandlerDelegate(EventHandlingMethodName);
The Subscribers are automatically unregistered when an object is disposed — if the subscriber was not already explicitly unregistered from the event. 9. Event Handling MethodAn 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 vs. 2.0+ ConsiderationsThe 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. 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. 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 GenericsIn addition to the Generic-specific features presented elsewhere in this article (e.g., 10.2 Delegate InferenceThe 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. 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);
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;
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., 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. 10.3 Anonymous MethodsAn 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. 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 Partial ClassesPartial 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 to go to section 15.1 which presents the implementation of events in partial classes in greater detail. 11. ConventionsThe 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 Event Publisher ConventionsEvent Name
Examples — for events raised before state change:
Examples — for events raised after state change:
System.EventArgs Subclass (where applicable)
Example
For events that contain no data, and never will, it is recommended to pass Event Handler (delegate) Name
Example custom event handler (delegate) names:
Event Handler (delegate) SignatureAs stated above, under "Delegate Name," you should use one of the The following recommendations are implemented in the
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
Example (uses built-in generic 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
Examples (each takes a custom OnDownloadCompleted(DownloadCompletedEventArgs)
{
// Raise event here
}
private OnTemperatureChanged(TemperatureChangedEventArgs)
{
// Raise event here
}
virtual OnMailArrived(MailArrivedEventArgs)
{
// Raise event here
}
11.2 Event Subscriber ConventionsEvent Handling Method Name
Examples:
Examples:
Event Handling Method Signature
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)
Example: m_MailMonitor.MailArrived += new EventHandler(
this.MailMonitor_MailArrived);
WARNING: Do not use the Unsubscribing from the Event (Code that Unregisters the Event Handling Method from the Event)
Example: m_MailMonitor.MailArrived -= new EventHandler(
this.MailMonitor_MailArrived);
11.3 Naming ConventionsCamel CasingCamel 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: Pascal CasingPascal 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: 12. Steps to Creating Custom EventsIn 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 Prepare the Event PublisherStep 1: EventArgs - Decide how your event will account for EventArgs.
Step 2: Event Handler - Decide which event handler your event will use.
Step 3: Declare the Event — Decide which syntax to use: field-like syntax or property-like syntax.
Step 4: Event-Raising Method — Decide whether you will raise the event from a method, or raise it inline.
Step 5: Raise the event.
12.2 Prepare the Event SubscriberBecause this article presents the event pattern ( Step 1: Write the Event Handling Method.
Step 2: Instantiate the Event Publisher.
Step 3: Instantiate the event handler (if necessary).
Step 4: Register the subscriber (event handling method) with the event.
Step 5: Unregister the subscriber (event handling method) from the event.
13. Sample Event ImplementationThis example makes use of a custom event handler, carries event data in a custom 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 Sample Event Publisher CodeStep 1: Subclass EventArgsHere we derive a new class, 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; }
}
}
Step 2: Event Handler (delegate)Here we declare a new delegate that conforms to the event conventions, returns public delegate void MoveFileEventHandler(object sender,
MoveFileEventArgs e);
Step 3: Declare the EventHere we declare the event using the field-like syntax. public event MoveFileEventHandler MoveFile;
Step 4: Event-Raising MethodHere 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"));
}
}
Step 5: Raise the EventHere 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 Sample Event Subscriber CodeStep 1: Write the Event Handling MethodThis method is called when the void fileMover_MoveFile(object sender, MoveFileEventArgs e)
{
MessageBox.Show(sender.ToString() + " moved the file, " +
e.FileName + ", from " +
e.SourceFolder + " to " + e.DestinationFolder);
}
Step 2: Instantiate the Event PublisherFileMover fileMover = new FileMover();
Step 3: Instantiate the event handler as we register the subscriber (event handling method) with the eventThis approach combines steps 3 and 4 from the steps listed in the section 14.2. | ||||||||||||||||||||||