Tips and patterns for C# events

Let’s see a couple of handy event tricks!

This article is also available on Medium.

Events are a great C# tool that helps to reduce the coupling between the different parts of your codebase. In short, they rely on the idea of designing some emitters that publish notifications for other objects to subscribe and react to.

However, being such a powerful feature of the language, they obviously also have some gotchas and interesting patterns worth mentioning…

So, today, let’s see a few cool tricks with C# events!

Handling events with no subscribers

Even if events are similar to delegates, there are still differences between the two; in particular, listening to events is optional. This is one of the big plus of events: they allow a very light coupling since you can emit events in a “fire-and-forget” way, without any subscriber on the other end.

But… if you ever tried to run a program where you invoke events without any subscribers, you probably got an Unhandled exception:

Note: this example is of course a bit “fake” since I’m emitting and receiving events on the same object, and I’m simulating some compute time with a for loop 🙂

Wait – what? Then how can we have event emitters with no listeners?

The trick is to use a nice idiom of the C# language: the ?.Invoke() syntax! Basically, by handing an interrogation mark ? before calling the event’s Invoke() method, we insure that if there is no subscribers for our event the emission still works without an exception:

Unsubscribing from anonymous callback functions

The previous example isn’t very realistic, because you hardly ever send messages “to yourself” (i.e. with objects that are both senders and receivers). Usually, you have some emitter (or broadcaster) on one side, and one or more receivers on the other end.

For example, suppose we have some MessageBroadcaster instance that invokes our Messaged event from time to time (with the same “fake time consuming” process as before), and a listener in our main function:

It’s not that complicated overall, but we have to note something crucial: whenever I link or unlink the listener (aka I “subscribe” and “unsubscribe”), I refer to the exact same event instance: b.Messaged.

It might seem obvious, but – since we’ve defined our callback as a lambda anonymous function, we have to make sure that we unsubscribed from this exact object… and not from a new instance of an exactly equivalent other lambda function!

In C#, you can’t fully insure equivalence of delegates created via anonymous functions; so the only way to subscribe and unsubscribe with the same lambda function instance is to store it and re-use the reference later (otherwise, you’d just unsubscribe from an on-the-spot temporary instance that wasn’t subscribed to anyway). That’s why I put it in my MessageBroadcaster object 🙂

Cancelling a series of events

If we continue our previous example, we can introduce another nice pattern of C# events: cancellation.

Sometimes, you send events at a very precise point in time, just to highlight that something specific happened. But other processes might also send multiple events at regular intervals, for example to warn other systems about an ongoing long process (e.g. listing files, converting data items…).

You can of course just go through a list of tasks, but you may also want to support an optional “early stop” system to abort the series of events. This is called “cancelling”.

To implement such a feature, you need to have your receivers “send back” some data to tell the emitter whether to continue or stop its process. The only problem is that, contrary to delegates, events can’t return values.

Roughly put, that’s because events are meant to accept multiple event subscribers… which would have some troubles knowing what the return value should be when used!

So, to communicate data “back” from the receivers to the emitter, we need to use another technique: we’re going to inject the info in the event arguments that are passed to the receiver, by modifying this object in place. This is done by adding a new boolean field to our MessagedArgs:

For this technique to work, you just need to make sure that the type of data your events send is defined as a reference type, i.e. a class and not a struct, like our MessagedArgs here. This way, you can declare your event arguments beforehand, pass them to the event and, if necessary, have those arguments modified by the subscriber.

Then, you can choose between two patterns:

  1. any subscriber can cancel the operation: you just prepare a false value for the boolean field, by default, and then any subscriber can override it to true if need be. Finally, the handler that is invoking the events checks this value to see if it should continue firing new events.
  2. conversely, if you want to cancel only when all subscribers request the operation cancellation, you just initialise the boolean field to true and let any one subscriber force it to continue by overriding it to false. Finally, you do the same sort of check as before in your handler.
    Be careful, though: with this second pattern, you have to insure you have at least one subscriber, otherwise you would get an invalid true cancellation!

In our case, if we use the first pattern for example, we could get this kind of logic to stop just after the first event:

As you can see, the really cool thing with this feature is that adding it causes no breaking change: subscribers are not required to use the cancellation mechanism (since the handler can work perfectly like before even if the field is never turned to true), so it’s very loosely coupled.

Note: you can use this “event arguments on-the-fly modification” pattern to track the current progress of a listing or a search, too! Simply store the total number of elements to look for and keep a counter on how many have been found. Then, in your receiver callbacks, increment the count field in your event arguments instance. Have a look at this tutorial in the Microsoft docs for more info 😉

Conclusion

Today, we saw a few handy tricks for using events cleverly, and following the C# best practices! Of course, those were just a couple of tips, and there are plenty more to learn in the field…

I hope you liked this short tutorial — and feel free to tell me in the comments what other C# topics you’d like to see me write about 🙂

Leave a Reply

Your email address will not be published.