Click here to Skip to main content
15,878,748 members
Articles / Programming Languages / C#

Tweaked Events

Rate me:
Please Sign up or sign in to vote.
4.83/5 (12 votes)
18 Dec 2010MIT18 min read 34.7K   289   41   3
Framework for customizing events. Comes with Weak Events and Synced Events

Introduction

C# events are one of my favorite language features. Coupled with .NET delegates, the observer pattern can be implemented so transparently that the pattern becomes invisible. And it's a very flexible feature. Apart from using different delegate signatures, it's possible to overload the add and remove operators in order to achieve a deep level of customization. Here, I'll describe a framework that I've created to help people customize events.

Quite a few observer pattern implementations have a common problem. If the observer fails to unregister from an observable, a strong reference to the observer will be kept in the observable. Garbage collection systems will not collect the forgotten observers until the observable is collected. This is one big source of memory leaks. C# events and Java listeners are both prone to this problem. Daniel Grunwald's Weak Events solve the observer leak problem using a customized event implementation. In his article, he discusses extensively how the weak event pattern can be implemented in C#. His solution is very reusable, but not very extensible. The code in the article you're reading was inspired by Daniel's work, and is an attempt to make it much more extensible.

Any customized C# event works by overriding the add and remove event accessors, and implementing an internal custom store for the event handler delegates. This technique can be used to customize events in many diverse ways. I created a base extensible framework that should be able to support a broad array of event types. I implemented a couple of these types, and let others suggested for future work. The framework is also able to merge behaviors from different event types facilitating the use of event decorators.

This is an open source initiative, so suggestion, bug fixes, and code additions are more than welcome. In the rest of the text, I'll show how to use my code, how it works, and how you can extend it. 

What is the Difference between Delegates and Events?

Before I proceed, I want to answer this question very clearly. The short answer is this. Events and delegates are completely different concepts. Delegates can be used without events, but events can't be used without delegates. Delegates are function objects. C# events are specialized properties with unusual accessors. Regular properties have get and set. Events have add and remove (even if you don't explicitly declare them). Accessors are nothing more than a set of methods bound together by the same name.

Looking at the accessors' names (add and remove), I could suppose that they are related to collections (or lists, sets, etc.). Being bolder, I'll dare to affirm that they are meant to be used only with collection-like data structures. In other words, I'm saying that events are interfaces to collections. Being more specific, I say that events are interfaces to a collection of delegates.

Alright, I know that non-customized events can only hold a single delegate, not a collection of them. Does that invalidate my statement? No, not really. Enter multicasting. Delegates have this very interesting ability of being merged into a single multicast instance. Such operations work in an immutable way, i.e., the addition (or subtraction) of two delegates generate a new delegate without modifying the operands. Strictly speaking, any delegate instance is an immutable collection of singlecast function objects. Invocation features, like multicasting, are built inside delegates, not events.

Then, a class that declares a non-customized event is declaring a protected delegate field and exposing two operations to it. External components will be able to add and remove only the delegates they know about. The code that has access to the delegate beneath the event is also able to invoke and reset its value.

Concepts / Vocabulary

I'll try to avoid confusion by using the vocabulary of definitions listed in this extensive article. I'll add a couple of my own.

Event Store - It's the data structure responsible for holding a collection of Event Entries (defined below). They must be invocable, just like a multicast delegate. Stores are also responsible for creating entries from delegates. Any class derived from TweakedEvent is an Event Store;

Event Entry - It's a single item that can be contained in an Event Store. It works like a singlecast function object. It's usually a wrapper around a delegate instance. Some entries can wrap other entries, working as decorators. Any subclass of BaseEventEntry is an Event Entry.

Using Tweaked Events

Before I get to the implementation details, let me show a basic example of how can you use Tweaked Events. Consider that you have an event source like the one below. It has a single event named StatusChanged, and an method (OnStatusChanged) that raises it. Naturally, the recommended pattern of invoking delegates is used.

C#
class RegularEventSource
{
	public event EventHandler StatusChanged;

	private void OnStatusChanged(EventArgs e)
	{
		var dlg = StatusChanged;
		if (dlg != null) dlg(this, e);
	}
}

Now, look at an equivalent code using Tweaked Events below:

C#
class TweakedEventSource
{
	private TweakedEvent<EventHandler> eventStatusChanged;

	public event EventHandler StatusChanged
	{
		add    { TweakedEvent.Add(ref eventStatusChanged, value); }
		remove { TweakedEvent.Remove(ref eventStatusChanged, value); }
	}

	private void OnStatusChanged(EventArgs e)
	{
		eventStatusChanged.Raise(this, e);
	}
}

Let's observe the differences. First, a private field has to be explicitly declared. It will hold our event store. My suggested naming practice for such fields is prefixing the name of the event with event. So, the field is named eventStatusChanged.

The second difference is the explicit implementation of the accessors (add and remove). In order to transparently handle null values and use proper synchronization, I created two static helper methods, TweakedEvent.Add and TweakedEvent.Remove. They will execute a lock-free synchronization that updates the tweaked event store passed by reference. I'll talk more about this synchronization later. These methods are recommended for all users of Tweaked Events. I don't currently see any reason to implement add and remove in any other way.

The last difference in this example is the event raising code. I created the Raise extension method that handles null values for you. Since it's an extension method, it's capable of handling null values for you. This simple trick can be done for regular delegates too.

The question now is: what have we achieved so far? Almost nothing. Both TweakedEventSource and RegularEventSource have the same behavior, with different overheads. The only difference might be the lock-free synchronization, if you are not compiling this with C# 4 (again, I'll get into this later).

So, if we want to get the real benefits of Tweaked Events, there's only a small step that has to be done. We have to choose an event type. Suppose we want to transform TweakedEventSource.StatusChanged into a weak event (just like Daniel's). All we need to do is initialize the event store field in the class constructor, as follows. When delegates are added, the event store will wrap then into WeakEventEntry instances. Every other detail works as it should.

C#
public TweakedEventSource()
{
	eventStatusChanged = new WeakEvent<EventHandler>();
}

Events Variations

The only event variation I mentioned so far was Weak Events. As I said, this is only one of many possible event variations. Later in this article, I will introduce Synced Events. There are many others, though. Here's a sample list of ways events can vary. The base code is probably able to cover all of them with very few modifications. If you envision a different type, please, tell me. I'd love to hear about it.

  • Event entry variations:
  • Event store variations:
    • Ordered invocations (perhaps with a priority queue)
    • Concurrent invocations (i.e., all event handlers called concurrently)
  • Exception handling:
    • Aggregate exceptions (as opposed to throwing the first exception that happens)
    • Attachable handler (e.g., for logging or auditing purposes)
  • Custom code access permission
  • Memento Events (it remembers all invocations; when a handler/entry is added, the past invocations are repeated to the new handler/entry)

Notice that some variations are typically implemented in event entries, whereas others are implemented in stores. In some complex cases, it might involve some coordination between both classes.

Tweaked Event Interfaces and Classes

Here is the code of the basic interfaces. They are ITweakedEvent and IEventEntry both in a generic and in a non-generic version. The former is meant for Tweaked Event Stores. The latter is meant for Tweaked Event Entries. Observe how immutability is enforced in the method signatures.

C#
public interface ITweakedEvent
{
	void Raise(object sender, EventArgs e);
}
	
public interface ITweakedEvent<TEventHandler> : ITweakedEvent where TEventHandler : class
{
	bool IsEmpty();
	ITweakedEvent<TEventHandler> Clear();

	ITweakedEvent<TEventHandler> Combine(TEventHandler handler);
	ITweakedEvent<TEventHandler> Subtract(TEventHandler handler);
	ITweakedEvent<TEventHandler> Combine(IEventEntry<TEventHandler> entry);
	ITweakedEvent<TEventHandler> Subtract(IEventEntry<TEventHandler> entry);
}

public interface IEventEntry : IEquatable<IEventEntry>, IDisposable
{
	/// <summary>Invoke the entry and returns the value of IsDisposed</summary>
	bool Invoke(object sender, EventArgs e);
	bool IsDisposed { get; }
}

public interface IEventEntry<TEventHandler> : IEventEntry where TEventHandler : class
{
	/// <summary>Compares this IEventEntry with a delegate for equality</summary>
	/// <param name="handler">A delegate to be compared with</param>
	/// <returns>True if this IEventEntry is equivalent to the handler</returns>
	bool EqualsHandler(TEventHandler handler);
}

Take notice of the generic constraint where TEventHandler : class. The constraint that would really fit is this: where TEventHandler : Delegate. Unfortunately, it is not a supported constraint in C# generics. The most restrictive constraint available is class. Anyways, the type of TEventHandler can be (and is) validated at runtime, in the static constructor of the base classes.

The code below is a summary of the TweakedEvent<> class. It contains the base implementation of ITweakedEvent. All current store implementations are subclasses of this abstract class. TEH is used as an abbreviation of TEventHandler.

C#
public abstract class TweakedEvent<TEH> : ITweakedEvent<TEH> where TEH : class
{
	//Constructors
	static TweakedEvent() { ... } // Will validate the type of TEH
	public TweakedEvent() { ... }
	protected TweakedEvent(IEventEntry<TEH>[] initialEntries) { ... }

	// Abstract Methods
	public abstract IEventEntry<TEH> CreateEntry(TEH handler);
	protected abstract ITweakedEvent<TEH> CreateNew(IEventEntry<TEH>[] entries);

	// Virtual Methods
	public virtual ITweakedEvent<TEH> Combine(TEH handler) { ... }
	public virtual ITweakedEvent<TEH> Subtract(TEH handler) { ... }
	public virtual ITweakedEvent<TEH> Combine(IEventEntry<TEH> entry) { ... }
	public virtual ITweakedEvent<TEH> Subtract(IEventEntry<TEH> entry) { ... }

	// Public Methods
	public bool IsEmpty() { ... }
	public ITweakedEvent<TEH> Clear() { ... }

	void ITweakedEvent.Raise(object sender, EventArgs e) { ... }
}

And here is the summarized code of the BaseEventEntry<> class. It's the base implementation of all event entries. The most important feature it provides is the implicit conversion to an event handler delegate. This feature enables any derived classes to be used as listener-side event wrappers.

C#
public abstract class BaseEventEntry<TEH> : IEventEntry<TEH> where TEH : class
{
	// Abstract Methods / IEventEntry<TEH> members		
	abstract public bool Equals(IEventEntry other);
	abstract public bool EqualsHandler(TEH handler);
	abstract public bool Invoke(object sender, EventArgs e);
	abstract public void Dispose();
	abstract public bool IsDisposed { get; }

	// Conversion to a TEventHandler delegate
	public static implicit operator TEH(BaseEventEntry<TEH> entry) { ... }

	public TEH ToEventHandler() { ... }
}

Tweaked Event features

In this section, I'll discuss some of the features that come with Tweaked Events framework out of the box.

Convertibility / Equatability

One important detail of Tweaked Event Stores is that they can only hold Tweaked Event Entries. They are not designed to hold delegates. In order to add delegates from an event store, we need to wrap them into event entries. Like I said earlier, event stores are responsible for converting delegates into event entries. Tweaked event store classes will override the CreateEntry method, which does this job.

Remove operations are trickier. There must be a way to match a delegate to be removed with one equivalent event entry. That's why all event entries are equitable to delegates. For remove operations to work, the EqualsHandler method has to be correctly coded in all event entry types.

Event Entry Composition / Decoration

Event entries are not restricted to be wrappers around delegates. In fact, anything that's invocable could be held inside an entry. Well, event entries are invocable themselves. So, what kind of monster is an entry that wraps other entries? An entry decorator is the answer! This kind of construction is almost the definition of the decorator pattern.

It's possible to write an entry type that can either wrap a delegate or another entry. The SynchEventEntry that I created follows this pattern. It's meant to invoke its inner entry (or delegate) in a synchronized way, i.e., it will actually run in another thread.

Immutability of Event Stores

One fact about delegates is that they are immutable. Just like numbers and strings, you can't alter a delegate. You can only create new ones. For example, you can add (or subtract) two delegates into a new third delegate. When a delegate is passed to the add accessor of a regular event, it's added to the current delegate inside the event, and the current event is updated.

Daniel's followed a different strategy in his Weak Events. They are mutable. The store field is designed to be initialized once and never to be overwritten. So, there's no need to worry about it being null or being substituted for another instance. Instead, entries are added (or removed) from its inner collection directly.

For reasons that I'll explain later, I decided to make Tweaked Events immutable. By that, I mean that event stores are immutable. Any store update (add, remove, clear) operation is composed by the creation of a new value and a subsequent update of the store variable. Regular events work in exactly the same way. Naturally, there are pros and cons of store immutability. The main differences revolve around concurrency and synchronization, so I'll discuss that now.

Synchronization in Add, Remove and Invoke operations for Immutable Event Stores

Immutable objects fit very well in multithreaded applications. No synchronization is needed inside them because nothing inside them will ever change. However, a variable can be used to share immutable objects. The objects themselves won't change, but the variable can be updated with a new object. Regular events work like this. Its shared value (i.e., a delegate) is immutable, but the value might change (on add and remove). In order to avoid concurrency issues, the access to the shared state has to be synchronized somehow.

Let's start with concurrent writes first. It happens when two or more threads are racing to write a new value to the same variable. The last one to finish might undo the work of the others. Depending on the purpose of the software component, this might be perfectly fine. In most cases, though, it's plainly incorrect. Events qualify as such cases. Two threads can race trying to add (or remove) handlers to the same event. Without proper synchronization, the resulting event might not contain one of the handlers, which is not correct.

Until C# 3, a simple lock was used to control access to the event accessors. Only one thread can update an event at a time. This approach is too simplistic. In C# 4, events got a little overhaul (as explained by Chris Burrows). A compare-and-swap lock-free mechanism is intrinsically implemented by the C# 4 compiler. This change introduces some breaking changes, but it's so much better then the older ones that its advantages outweigh the breaks it might insert in working code. I also wrote an article about this lock-free synchronization here.

Multiple reads in sequence of a shared variable may cause another problem. If the shared state is updated between the reads, unexpected things might happen. That's why there's a recommended pattern for invoking event delegates. You should read the delegate only once, and do all operations (check for null and invocation) using the copied reference.

In order to solve all these problems, Tweaked Events have three static methods (TweakedEvent.Add, TweakedEvent.Remove, and TweakedEvent.Raise). All of them are thread-safe and null-safe. The first two mimic the lock-free synchronization mechanism implemented in C# 4 events. The latter method provides a simple safe way to invoke tweaked events.

Synchronization for Mutable Event Stores

Let's now compare how mutable event stores have to deal with the concurrency problems I mentioned above. In add and remove, a compare-and swap can't be used. The variable holding the event store doesn't change, it's the event store itself that changes. An entry will be either added or removed from there. Adding or removing items from a mutable list might cause an internal allocation of new resized array. This is definitely not an atomic operation, so a lock has to be used. The advantage is that the operation may be very fast to execute, while an immutable store is almost obliged to copy every entry for every add or remove. One is faster, the other doesn't need locks.

Before taking a side, let's look at invocation. We saw that all that's needed to invoke an immutable stores is to ensure a single read. We keep a reference to the current store in a local variable and that's it. On the other hand, mutable stores might change while the invocation is going on. A lock around the invocation code could solve the problem, but it creates another. If we do that, the event will be locked while user code is running. This is very dangerous. User code can take an arbitrarily amount of time to execute. Locking has to occur only for very short periods. The only alternative is creating a temporary local copy of all entries, and running the invocation on the copy. Of course, a lock is needed during the copy.

Here's a summary, if you got lost in my reasoning.

OperationImmutable StoreMutable Store
Add/removeFull copy of entries every timeVery fast most of the time
Lock-free compare-and-swapLocks required
InvokeSimple reference copyFull copy of entries every time
Lock-freeLock required around the copy

Immutable store might be closely defeated on add and remove, but they definitely are better at invocation. If we consider that invokes are much more common than adds or removes, immutability wins easily. And there's one last comment I would like to make on this subject. I said that immutable stores require a full copy in add and remove. This is not completely true. Operation on immutable structures don't always require a deep cloning of its content. This is a complex subject, though. If you are interested, I suggest you read Eric's posts.

New Tweaked Event Stores Implementations

This framework is designed to be extensible. New event types can and should be implemented. Although immutability is not enforced, development of new event types have to have immutability in mind. State change in stores and entries is strongly discouraged, but it might be acceptable is some situations. Naturally, restrictions are not a good thing. If you feel uncomfortable to code immutable structures now, you might learn to love their advantages later. For one, they are naturally thread-safe (at least internally).

Listener-side Tweaked Events

Have you ever wished you could add a customized event entry to a regular event? There are times when you simply can't change the event source. Until now, I only talked about source-side events. There's another side of the story, though. Some situations call for listener-side events. I'm proud to say that Tweaked Events support that neatly.

Looking from the outside, events accept only delegate of a given type. Suppose you have a delegate handler and wants to tweak it somehow (e.g., make it a weak delegate).The best that can be done is to put it inside a wrapper object. Then you create a delegate that points to a method of the wrapper, and pass this delegate to the event. Such a solution is detailed discussed by Daniel Grunwald suggested in his WeakEvents article. Sacha Barber also used a weak event entry wrapper (the WeakEventProxy class) in his CinchV2. Both of them created separate implementations for listener-side events and source-side events. In Tweaked Events, the same code solves both problems.

Just as the others, Tweaked Events support listener-side events through wrapper objects. The difference is that Tweaked Event Entries are wrappers. The base event entry class (BaseEventEntry) has the ToEventHandler method that returns a delegate that can be added to any event. This way, any tweaked event entry implementation will support listener-side events without a single extra line of code.

C#
//This will add a regular delegate
button.Click += Button_Click;

//This is a regular delegate
button.Click += TweakedEvent.ToWeak(Button_Click);

Synchronized Events (Synced Events, For Short)

If you are (or were) a Windows Forms developer, you probably learned that all access to Controls has to be synchronized. WinForms controls are not designed to support requests coming from arbitrary threads. All requests have to be executed in the same thread were the control handle was created. This is a very tricky subject full of gotchas, and you don't need any knowledge of it to understand my following example.

Suppose you build a simple WinForms application that has a single window. This window has a button that, when pressed, will cause a long operation to be executed. You don't want to block the UI, so the operation will run asynchronously. When it finishes, you want to update some other controls in that window. Since you can't update the controls from a background thread, you'll have to marshall the code to the UI thread. You can achieve that by calling the Control.Invoke method.

Now, suppose you used something like BackgroundWorker to run the asynchronous task. The code that would update the UI would naturally be placed in an handler of the RunWorkerCompleted event. Imagine that the BackGroundWorker class assumed that all handlers of the RunWorkerCompleted event were going to make UI updates. It could automatically call the handlers in a synchronized way. By doing this, the user code would not have to worry about doing the synchronization stuff. Of course, you probably know that BackgroundWorker is indeed coded like this. Both RunWorkerCompleted and ProgressChanged events are called in a synchronized way by means of a SynchronizationContext.

What if you need the async operation coming from a different source like a WCF service. Actually, any network messages will usually come to your application asynchronously. In order to reflect this change in the UI, synchronization is necessary.

Does all this apply only to WinForms? Of course not, WPF UI updates also have to be synchronized. And it's not necessarily restricted to UI. The SystemEvents class is one example. All its events are synchronized, and it can be used in applications of any kind.

If you are implementing an event source class and wants to provide synced events, Tweaked Events and a few extra lines of code are all you need. If you are at the listener side, a very small tweak will enable synchronization in your code.

Future Work

New event types will be implemented as needed. Despite that, the most critical task at hand is to improve the unit tests. The existing ones are barely a start. When I started this code, I wasn't a loyal adopter of TDD. If you liked my code and want to contribute in any way, you are very welcome to contact me.

History

  • 2010.09.04 - Initial submission
  • 2010.12.17: Unit tests added
    • Test frameworks changed to xUnit (xunit.codeplex.com) and Moq (code.google.com/p/moq)
    • Test coverage of main classes is about 70%
    • A couple of minor bugs were found and corrected

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer (Senior) ThoughtWorks
Brazil Brazil
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralGood job Pin
Sacha Barber19-Dec-10 23:29
Sacha Barber19-Dec-10 23:29 
GeneralRe: Good job Pin
jpbochi20-Dec-10 3:38
professionaljpbochi20-Dec-10 3:38 
GeneralRe: Good job Pin
Sacha Barber20-Dec-10 4:59
Sacha Barber20-Dec-10 4:59 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.