Introduction
In this article, you will learn about delegates and how to write delegate handlers using functions, anonymous functions and Lambda expressions. This article is written from a beginner's perspective and contains very introductory material on the related topics.
Background
Many a times, we need one part/object of our application to do something when some other part/object has been changed. One way to achieve this is by having an object poll continuously to another object and act whenever the other objects state is changed. Now this approach is not elegant, it involves too much of CPU usage. Also, there will be some time interval between 2 consecutive poll requests and if the other objects state was changed during this period, then we will get to know it later, i.e., delayed information.
Now, the way this problem is solved is called the Observer Pattern. Here, the object which wants to listen to the state change can subscribe to the object whose state will be changed. If we need to put this mechanism in a C# application, then we need to take care of both aspects of this pattern. One is sending the state change from an object to the listener(all the listeners) and receiving the state change of the object.
Delegates
is the C# way of implementing the first part, i.e., keeping a track of all the listeners and sending them the notification when any state is changed. Anonymous
methods and Lambda
expressions come on the other side, i.e., handling the response coming from a delegate.
Using the Code
Let us try to implement a simple application with a class called BatteryLevel
to understand these topics in action. This class will keep track of the current battery level of a device. Other classes can subscribe to a delegate
provided by this class to listen to battery level change. Let us look at this class without any delegates introduced.
class BatteryLevel
{
int currentBatteryLevel;
public int CurrentBatteryLevel
{
get
{
return currentBatteryLevel;
}
set
{
currentBatteryLevel = value;
}
}
}
To implement delegates and its handlers, there are mainly four steps involved:
- Declaring the delegate
- Creating the delegate
- Hooking the delegate with handler functions
- Invoking the delegates
Now before adding a delegate to this class, let's get some basics clear. Delegates are types which we can define. The definition will declare the delegate and specify the method signature that this delegate can hold and call. Typical delegate declaration will look like:
[Access Modifier] delegate [Return type] [DELEGATE_NAME]([Function arguments]);
So to declare a delegate for our class, let's say we want the handler functions to return void
and accept an integer value indicating the new battery level. The declaration will look like:
public delegate void BatteryLevelBroadcaster(int batteryLevel);
Delegates can hold reference to a number of methods and when a delegate is invoked, it will call all the methods one by one sequentially. C++ programmers can think of delegates as a list of type safe function pointers.
Now we have seen the first part of our four step process. Now if we move on to the second part, i.e., creating the delegate, we simply need create a type of the delegate and assign some handler functions to it. If no handlers are associated with the delegate object, it will be null
. So let's create a delegate type first.
public BatteryLevelBroadcaster batteryLevelBroadcaster;
Now we have seen the second part of our 4 step process. Now if we move on to the fourth part, i.e., invoking the delegate. The typical way to call all the functions associated with this delegate would be to call the delegate like a function.
if (batteryLevelBroadcaster != null)
{
batteryLevelBroadcaster(currentBatteryLevel);
}
So now the BatteryLevel
class definition with the delegate will look like:
class BatteryLevel
{
int currentBatteryLevel;
public delegate void BatteryLevelBroadcaster(int batteryLevel);
public BatteryLevelBroadcaster batteryLevelBroadcaster;
public int CurrentBatteryLevel
{
get
{
return currentBatteryLevel;
}
set
{
currentBatteryLevel = value;
if (batteryLevelBroadcaster != null)
{
batteryLevelBroadcaster(currentBatteryLevel);
}
}
}
}
Now we have seen the invoker objects part of implementing delegates, i.e., declaring, creating and invoking a delegate. Now let us see how the handing part can be written, i.e., hooking the functions to the delegates and having the handler function's body.
To hook a function to the delegate, we simply need to create the delegate type with the function name passed in the constructor. We can then assign this delegate type to the delegate exposed by the class.
static void Main(string[] args)
{
BatteryLevel batteryLevel = new BatteryLevel();
batteryLevel.batteryLevelBroadcaster +=
new BatteryLevel.BatteryLevelBroadcaster(BatteryLevelIndicator);
batteryLevel.CurrentBatteryLevel = 35;
batteryLevel.CurrentBatteryLevel = 30;
batteryLevel.CurrentBatteryLevel = 25;
batteryLevel.CurrentBatteryLevel = 20;
batteryLevel.CurrentBatteryLevel = 15;
}
public static void BatteryLevelIndicator(int newValue)
{
Console.WriteLine("New battery level is: {0}", newValue);
}
The above code simply creates a function and then attach that function to the delegate exposed by the class as the handler function. The function signature matches the delegate signature, otherwise the code would not compile.
We are also setting the value of battery level to test the application. Whenever we call set, our handler function will be invoked and it will print the message on the console.
Note: We have used +=
to assign the delegate because we need our function to add to the list of functions already added to this delegate. If we simply use =
sign, it will wipe off the list of existing functions and have this function as the only function associated with the delegate.
So we have seen the 3rd part of our four step process, i.e., hooking up the handler functions with the delegates. When we run the application, we can see the handler function getting called via the delegate.
Anonymous Functions
Anonymous functions are the functions without any function name. C# gives us the possibility of creating functions without name. But how will this function be called if it has no name. In fact, there is no way to call an anonymous function via code because the name of the function is the handle to call this function.
But we have just seen that the delegates keep handles/references of all the handler functions with them. Delegates don't need the name of the function to invoke them, so the anonymous functions are useful with the delegates only.
What anonymous function does is that instead of having separate code to hook the handler to delegate and body of the handler function, we can simply have the body written in place where we are hooking the function to the delegate. So if I need to achieve the above functionality using anonymous function, I simply need to write it as:
static void Main(string[] args)
{
BatteryLevel batteryLevel = new BatteryLevel();
batteryLevel.batteryLevelBroadcaster += delegate(int newValue)
{ Console.WriteLine("(ANOMYMOUS)New battery level is: {0}", newValue);};
batteryLevel.CurrentBatteryLevel = 35;
batteryLevel.CurrentBatteryLevel = 30;
batteryLevel.CurrentBatteryLevel = 25;
batteryLevel.CurrentBatteryLevel = 20;
batteryLevel.CurrentBatteryLevel = 15;
}
And it will produce the same result as the earlier version but it doesn't need the programmer to write a separate function. This is very useful when the handler function has little code and using this approach gives a cleaner code compared to the earlier version.
Lambda Expressions
Newer version of C# has Lambda expression which has made the anonymous functions more or less obsolete. For the scope of this article, let's understand lambda expression is one more way of creating local anonymous functions that can be used with delegates.
Note: Lambda expression has other merits over anonymous function like it can directly be assigned to expression trees, but we will keep things simple in this article and will not talk about these.
A Lambda expression can be thought of as an alternative to the anonymous methods with the following syntax:
[METHOD PARAMETERS] => [METHOD BODY]
So if we were to write the similar code using lambda expression, we can do that in the following manner:
static void Main(string[] args)
{
BatteryLevel batteryLevel = new BatteryLevel();
batteryLevel.batteryLevelBroadcaster +=
(newValue) => Console.WriteLine("(LAMBDA)New battery level is: {0}", newValue);
batteryLevel.CurrentBatteryLevel = 35;
batteryLevel.CurrentBatteryLevel = 30;
batteryLevel.CurrentBatteryLevel = 25;
batteryLevel.CurrentBatteryLevel = 20;
batteryLevel.CurrentBatteryLevel = 15;
}
Note: In this above code, instead of having the implicit type parameter in lambda expression, we could have specified the type in the expression too.
batteryLevel.batteryLevelBroadcaster +=
(int newValue) => Console.WriteLine("(LAMBDA)New battery level is: {0}", newValue);
The result of the above code is the same as the earlier versions but the code is much cleaner compared to the earlier versions (one with the handler functions and anonymous methods).
Point of Interest
In this small article, we tried to look into what delegates are and how we can implement custom delegates and their handlers. Delegates have been around from quite some time and this article seems quite late or out of time. I have written this article because someone in the Codeproject Q&A section seems quite confused about the topic. This article has been written from a beginner's perspective. I hope this has been somewhat informative.
History
- 6th December, 2012: First version