C# Delegates and Events

C# Delegates and Events

Author: damir June 8, 2017

When I started learning C# the difference and relation between delegate and event was not so clear to me. After checking few books and lots of googling I realized that I am not the only one. :)

Some resources were correct in their description but lacked the comprehensive examples, other were completely wrong or just very unclear.

In this article the same task will be implemented using only delegate, and then using both delegate and event. So the difference, relation between delegate and event and the advantage of using the event will be clear.

It is expected that reader of this article is familiar with basic C# and has some knowledge about delegates and events.

Delegate

A delegate is a type that represents references to methods with a particular parameter list and return type. When you instantiate a delegate, you can associate its instance with any method with a compatible signature and return type. You can invoke (or call) the method through the delegate instance.

https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/delegates/

Please note that delegates can be chained – on single call multiple methods will be called.

Event

Events enable a class or object to notify other classes or objects when something of interest occurs. The class that sends (or raises) the event is called the publisher and the classes that receive (or handle) the event are called subscribers.

https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/events/

Task

We will create one lemming and three watchers that will monitor its health and respond as soon as health changes.

Delegate example

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace delegate_only
{
	public delegate void LemmingChanged(Lemming lemming);

	public class Lemming
	{
		private int health;

		public int Health
		{
			get { return health; }
			set
			{
				health = value;

				if (LemmingChanged != null)
				{
					LemmingChanged(this);
				}
			}
		}

		public LemmingChanged LemmingChanged;
	}

	class LemmingWatch
	{
		public void LemmingChangedHandler(Lemming lemming)
		{
			Console.WriteLine($"LemmingWatch lemming changed. Health=({lemming.Health})");
		}
	}

	class AnotherLemmingWatch
	{
		public void LemmingChangedHandler(Lemming Lemming)
		{
			Console.WriteLine($"AnotherLemmingWatch Lemming changed. Health=({Lemming.Health})");
		}
	}

	class YetAnotherLemmingWatch
	{
		public void LemmingChangedHandler(Lemming Lemming)
		{
			Console.WriteLine($"YetAnotherLemmingWatch Lemming changed. Health=({Lemming.Health})");
		}
	}

	class Program
	{
		static void Main(string[] args)
		{
			Lemming Lemming = new Lemming() { Health = 99 };

			LemmingWatch LemmingWatch = new LemmingWatch();
			AnotherLemmingWatch anotherLemmingWatch = new AnotherLemmingWatch();

			Lemming.LemmingChanged += LemmingWatch.LemmingChangedHandler;
			Lemming.LemmingChanged += anotherLemmingWatch.LemmingChangedHandler;

			Console.WriteLine("Change Health:");
			Lemming.Health = 80;

			Console.WriteLine("\n--\n");

			// LemmingChanged not encapsulated 
			Console.WriteLine("LemmingChanged not encapsuled: Loose previous chained methods by mistake:");

			YetAnotherLemmingWatch yetAnotherLemmingWatching = new YetAnotherLemmingWatch();
			Lemming.LemmingChanged = yetAnotherLemmingWatching.LemmingChangedHandler; // loosing previous chained methods - mistake '=' instead of '+='
			Console.WriteLine("Change Health:");
			Lemming.Health = 51;
			

			Console.ReadLine();
		}
	}
}

Output

Change Health:
LemmingWatch lemming changed. Health=(80)
AnotherLemmingWatch Lemming changed. Health=(80)

--

LemmingChanged not encapsuled: Loose previous chained methods by mistake:
Change Health:
YetAnotherLemmingWatch Lemming changed. Health=(51)

As you can see delegate is public – for now, this is necessary in order to enable adding event handler from outside of the class.

public LemmingChanged LemmingChanged;

Two event handlers are successfully added:

Lemming.LemmingChanged += LemmingWatch.LemmingChangedHandler;
Lemming.LemmingChanged += anotherLemmingWatch.LemmingChangedHandler;

But there is mistake when adding the third one – and two previously added event handlers are removed by simple typing mistake (= instead of +=):

Lemming.LemmingChanged = yetAnotherLemmingWatching.LemmingChangedHandler;

Also since delegate is public – it is possible to evoke the event from the outside of the class and evoke event handlers although requirements for raising the event are not met.

Event example

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace delegate_with_event
{
	public delegate void LemmingChanged(Lemming lemming);

	public class Lemming
	{
		private int health;

		public int Health
		{
			get { return health; }
			set
			{
				health = value;

				if (lemmingChanged != null)
				{
					lemmingChanged(this); // use private delegate
				}
			}
		}

		private LemmingChanged lemmingChanged; // changed to private

		// use event to add and remove event handler to/from delegate
		public event LemmingChanged LemmingChanged
		{
			add
			{
				lemmingChanged += value;
			}
			remove
			{
				lemmingChanged -= value;
			}

		}


	}

	class LemmingWatch
	{
		public void LemmingChangedHandler(Lemming lemming)
		{
			Console.WriteLine($"LemmingWatch lemming changed. Health=({lemming.Health})");
		}
	}

	class AnotherLemmingWatch
	{
		public void LemmingChangedHandler(Lemming Lemming)
		{
			Console.WriteLine($"AnotherLemmingWatch Lemming changed. Health=({Lemming.Health})");
		}
	}

	class YetAnotherLemmingWatch
	{
		public void LemmingChangedHandler(Lemming Lemming)
		{
			Console.WriteLine($"YetAnotherLemmingWatch Lemming changed. Health=({Lemming.Health})");
		}
	}

	class Program
	{
		static void Main(string[] args)
		{
			Lemming Lemming = new Lemming() { Health = 99 };

			LemmingWatch LemmingWatch = new LemmingWatch();
			AnotherLemmingWatch anotherLemmingWatch = new AnotherLemmingWatch();

			Lemming.LemmingChanged += LemmingWatch.LemmingChangedHandler;
			Lemming.LemmingChanged += anotherLemmingWatch.LemmingChangedHandler;

			Console.WriteLine("Change Health:");
			Lemming.Health = 80;

			Console.WriteLine("\n--\n");

			// LemmingChanged not encapsulated 
			Console.WriteLine("Add 3rd event handler:");

			YetAnotherLemmingWatch yetAnotherLemmingWatching = new YetAnotherLemmingWatch();
			// impossible to make mistake: C# error: event Lemming.LemmingChanged can only appear on the left hand side of += -=
			//Lemming.LemmingChanged = yetAnotherLemmingWatching.LemmingChangedHandler; 
			Lemming.LemmingChanged += yetAnotherLemmingWatching.LemmingChangedHandler;
			Console.WriteLine("Change Health:");
			Lemming.Health = 51;


			Console.ReadLine();
		}
	}
}

Output

 Change Health:
 LemmingWatch lemming changed. Health=(80)
 AnotherLemmingWatch Lemming changed. Health=(80)

--

Add 3rd event handler:
 Change Health:
 LemmingWatch lemming changed. Health=(51)
 AnotherLemmingWatch Lemming changed. Health=(51)
 YetAnotherLemmingWatch Lemming changed. Health=(51)

Delegate is now private:

private LemmingChanged lemmingChanged;

Using event to add or remove event handler to or from delegate is very similar as using properties to get and set value to member variables.

Just instead of set/get,  add/remove is used. :)

public event LemmingChanged LemmingChanged
{
	add
	{
		lemmingChanged += value;
	}
	remove
	{
		lemmingChanged -= value;
	}
}

Adding and removing the event handlers is done through event. It is not possible to access delegate directly from outside of the class.

And if we try to clear previously added event handlers by using = instead of += as in previous example – it is just not possible:

Lemming.LemmingChanged = yetAnotherLemmingWatching.LemmingChangedHandler; 

C# will report error:

event Lemming.LemmingChanged can only appear on the left hand side of += -=

Conclusion

Events are heavily relay on delegates. In fact relationship between events and delegates is very similar as relationship between properties and member variables. Events provide safe and friendly way of using delegates.

They enable adding and removing event handlers from the outside of the class without allowing bad things to happen. Using events it is not possible to clear all event handlers by mistake or to raise the event from outside of the class (this is important because it is class responsibility to invoke the delegate when some class internal conditions are met).

By using events as a “wrapper” for delegate you encapsulation is not broken and you keep the needed flexibility. You can have you cake an eat it. :)

Author
damir

    Leave a Reply

    Your email address will not be published. Required fields are marked *