Events Part 1 - Food for thought

Events

Event - a notification of a an occurrence or incidence, in programming terms, almost always in the past. Events are not always intuitive or easy to follow since they jump out of the natural control flow.
If we consider approaches to extend functionality without uglifying your class immediate thought goes to Dependency Injection. Whether it is a service or a Decorator, they now become the dependencies of your class, albeit through Inversion of Control. I am not saying this is bad, but raising events do not get a fair consideration.

Yet in so many cases they present as a great choice to extend functionality by reacting to them and even pass data out through to consuming clients. Yes, they can serve as a great vehicle to send data out. If you consider a method which returns void, but raises an event, then even though returning void is able to communicate or send out data through events. Neat.

Here I would be discussing on how to leverage events and improve code instead of delving into semantics of events and delegates in the DotNet framework. I intend this post to serves as a food for thought on using events to decouple behaviour.
Events enable loose coupling and promote separation of concerns. You can delegate (no pun intended) all external processing to the event handlers such as logging, notifications or calls to other services. The event could have multiple subscribers to it. It is a good way to update a subject’s dependents. As you can see, events need not be restricted to UI programming, but are also equally suitable for business components.
Let is consider a product domain. I’ll keep the domain logic simple and mundane so that we can focus on events and its usage. We wish to raise an event for each new product that gets added. The handlers(subscribers) who are interested in this event can then react to this.

Product(Subject)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class ProductSync
{
public event EventHandler<ProductSyncEventArgs> Saved;

// simulate data store
private readonly IDictionary<string, ProductSync> _productRepository;

public ProductSync()
{
_productRepository = new Dictionary<string, ProductSync>();
}

public string Name { get; set; }

public int Quantity { get; set; }

public void Add(string name, int quantity)
{
if (_productRepository.ContainsKey(name))
{
_productRepository[name].Quantity = quantity;
Console.WriteLine("Thread# {2}: {0} quantity updated to {1}",
name, quantity, Thread.CurrentThread.ManagedThreadId);
// no event will be raised here
return;
}
var p = new ProductSync() { Name = name, Quantity = quantity };
_productRepository.Add(name, p);
Console.WriteLine("Thread# {1}: New Product added - {0} ",
name, Thread.CurrentThread.ManagedThreadId);
OnSaved(p); // event raised
Console.WriteLine("Thread# {1}: Completed add for {0}",
name, Thread.CurrentThread.ManagedThreadId);
}

protected virtual void OnSaved(ProductSync p)
{
// check if we have any handlers listening
// and call them. Below is a short hand to do that.
(Saved as EventHandler<ProductSyncEventArgs>)?.Invoke(this, new ProductSyncEventArgs(p));
}
}

public class ProductSyncEventArgs : EventArgs
{
public ProductSyncEventArgs(ProductSync product)
{
Product = product;
}
public ProductSync Product { get; private set; }
}

I have deliberately appended Sync to the name. This is to highlight that event handlers subscribing to an event are invoked synchronously. The execution path does jump from one method to another, but the calls are blocking. I have included the thread id to show where the publishers and the subscribers are executing. This is important so that we do not have any misleading assumptions since the execution flow passes to the handlers. We also have a class ProductSyncEventArgs which inherits from EventArgs. This is a standard approach to pass arguments through events within DotNet.

Let us create a few subscribers or event handlers which need to do some work when event is raised.

Handlers (Subscribers)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public class NotifyHandler
{
readonly ConsoleWriter cw;
public NotifyHandler()
{
cw = new ConsoleWriter();
}
public void SendMail(object source, ProductSyncEventArgs e)
{
Thread.Sleep(1000);
cw.Write("Thread# {1}: Sending email for product {0}",
ConsoleColor.Red, e.Product.Name,
Thread.CurrentThread.ManagedThreadId.ToString());
}
}

public class ExternalCallHandler
{
readonly ConsoleWriter cw;
public ExternalCallHandler()
{
cw = new ConsoleWriter();
}
public void DoCall(object source, ProductSyncEventArgs e)
{
Thread.Sleep(1000);
cw.Write("Thread# {1}: New note added for {0}",
ConsoleColor.Green, e.Product.Name,
Thread.CurrentThread.ManagedThreadId.ToString());
}
}

public class QuantityAuditHandler
{
readonly ConsoleWriter cw;
public QuantityAuditHandler()
{
cw = new ConsoleWriter();
}
public void Audit(object source, ProductSyncEventArgs e)
{
Thread.Sleep(1000);
cw.Write("Thread# {2}: Auditing for {1}. Quantity: {0}",
ConsoleColor.Yellow, e.Product.Quantity.ToString(), e.Product.Name,
Thread.CurrentThread.ManagedThreadId.ToString());
}
}

//helper to colour code console outputs
public class ConsoleWriter
{
public void Write(string message, ConsoleColor colour, params string[] args)
{
Console.ForegroundColor = colour;
Console.WriteLine(message, args);
Console.ResetColor();
}
}

The implementations are self-explanatory. I have added Thread.Sleep to simulate a load and helper class to output colour coded text to indicate some behaviour.

Application (Client)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Program
{
static void Main(string[] args)
{
var handler1 = new NotifyHandler();
var handler2 = new ExternalCallHandler();
var handler3 = new QuantityAuditHandler();

var product = new ProductSync();
// registering handlers
product.Saved += handler1.SendMail;
product.Saved += handler2.DoCall;
product.Saved += handler3.Audit;

product.Add("Cadbury classic", 10);
product.Add("Lindt Dark 80%", 10);
product.Add("Mars", 10);
product.Add("Cadbury classic", 10);
// unregistering handlers
product.Saved -= handler1.SendMail;
product.Saved -= handler2.DoCall;
product.Saved -= handler3.Audit;
}

When you run the program you get the below output. It clearly shows the synchronous execution and how the Add call is blocked until all the handlers have not returned.
Program output

When a product is now added there can be other actions executed without having to inject external dependencies to the Product class. I would however like to point out there are a few draw backs in implementing events this way. Astute readers would have already realised that what we have seen is DotNet’s inbuilt implementation of Observer pattern and most of these drawbacks are inherit to Observer pattern.

  • Multiple subscribers and publishers make it difficult to maintain and understand code.
  • Subscriber and publishers need to know each other. This tight coupling will not allow each to vary independently.
  • The publisher holds strong references of each observer. This might create memory leaks when the subscriber does not de-register themselves.

Here I would not go into addressing each of these issues, but these could be solved using Mediator pattern or Event Aggregate pattern. Again a warning, both these patterns at first glance might seem interchangeable or have the same intent, but they are not. Mediator helps in communicating between unrelated objects, which require co-ordinations. It is not mandatory to use Event type with in Mediator. You would see behaviour inside Mediator. Event Aggregators as the name suggests deal only in maintaining subscriptions and publishing of events. Once again it need not be done using Event type. You can also manage the references of subscribers to avoid memory leaks. Of course all this would mean more work upfront.
Well, like I had mentioned, this post was supposed to be a food for thought and use of events in modelling business logic.
In a continuing post I’ll explore how we can call the event handlers asynchronously.

Do let me know your thoughts through comments on how you have used events in your business layers?