Delegates were introduced with C# 1.0 (with .NET 1.0) as a way to represent function pointers. Delegates like most other aspects of advanced C#/.NET programming were often misunderstood and a lot of times programmers didn't even know they were using them inside their code! One source of this problem was a rather cluggy syntax provided with the original C# 1.0 syntax. Luckily as .NET evolved, so did the syntax for delegates by becoming more concise and clear. This is exactly what this article focuses on; this is NOT an intro into delegates, how they relate/work with events or a "best practice" guide.
The best way to show delegate syntax in action is to try to solve a simple coding problem with them. Suppose I get asked this on an interview: I have a form with 2 text boxes and I can input 2 numbers. What I would like to do is when I click the "=" button (equals) is have the numbers be added via a delegate binary operation, by adding the two numbers.
So the form looks something like this:
Let's assume that we have an event handler already hooked up to the button and after we click that button we expect the two numbers to be added.
.NET 1.x (1.0 - 1.1) Solution (C# 1.0)
This solution has four parts:- Declare the delegate with the desired signature
- Declare a method that matches the delegate signature
- Instantiate the delegate (tell the delegate with method to call)
- Call the delegate and retrieve the result
Delcare the delegate:
delegate int Add(int firstNumber, int secondNumber);
In the code above, what we did was create a template in a sense of how we want our method to look like. We do not know what this method will do yet, but we do know it will have two integers as parameters and it will return an integer.
Declare a method that matches the delegate signature:
private int AddNumbers(int firstNumber, int secondNumber)
{
return firstNumber + secondNumber;
}
Instantiate the delegate (tell the delegate with method to call)
Add add = new Add(this.AddNumbers);
Call the delegate and retrieve the result
int result = add(Convert.ToInt32(this.txtFirst.Text),Convert.ToInt32(this.txtSecond.Text));
For those confused, you can just call the delegate directly. For example:
int result = add(3, 4);
Summary
Using delegates in .NET 1.0 - 1.1 (2002 - 2003) was not the friendliest. There were a few steps to go through. Many programmers taking the leap from VB.NET or another language simply by passed delegates because of the additional syntax. Imagine learning a new language and trying to conceptualize using delegates and their uses. Delegates in these early days of .NET were usually used/abused by C++ programmers familiar with the delegate C++ counterpart. Delegates also in those days had some performance issues; design implications (making a public delegate is bad) and some designs could cause memory problems.Putting it all together would look something like this:
1 // 1) Declare a delegate
2 delegate int Add(int firstNumber, int secondNumber);
3
4 // 2) Declare a method (matches the delegate signature)
5 private int AddNumbers(int firstNumber, int secondNumber)
6 {
7 return firstNumber + secondNumber;
8 }
9
10 private void btnAdd_Click(object sender, EventArgs e)
11 {
12 // 3) Initialize the delegate to a method that matches the signature
13 Add add = new Add(this.AddNumbers);
14
15 // 4) retrieve the result
16 int result = add(Convert.ToInt32(this.txtFirst.Text),Convert.ToInt32(this.txtSecond.Text));
.NET 2.0 - 3.0 Solution (C# 2.0)
.NET 2.0 introduced a concept called anonymous methods. This allowed us to call inline business logic (i.e., a method) without having to have the method defined. The business logic simply became part of the delegate. Let's see how this improved our delegate syntax for our scenario.This solution has three parts:
- Declare the delegate with the desired signature
- Instantiate the delegate (with the business logic defined inside the delegate "body")
- Call the delegate and retrieve the result
Delcare the delegate:
delegate int Add(int firstNumber, int secondNumber);
Instantiate the delegate (with the business logic defined inside the delegate "body")
Add add = delegate(int firstNumber, int secondNumber)
{
return firstNumber + secondNumber;
};
So, this is nice and clear and we eliminated a whole part (no more method to declare) and one statement of code. This is what anonymous methods allow us to do, by putting the method logic directly inside the "body" of the delegate. The syntax for the anonymous method starts with the word delegate itself after the equal sign. In this case, it tells the compiler that we are declaring an anonymous method. The next step is to define the parameters to match the signature of our Add method, which are the two intergers respectively. Our inline body of our delegate is the same as our AddNumbers method returning an integer. That's all there is to it. Anonymous methods can have no parameters and as well can have no (void) returns.
Call the delegate and retrieve the result
int result = add(Convert.ToInt32(this.txtFirst.Text),Convert.ToInt32(this.txtSecond.Text));
Summary
Using delegates in .NET 2.0 - 3.0 became simpler. Not just because you eliminated one line of code, but because now it had one less "working part" and programmers not familiar with delegate syntax could easily tell what was going on. This solution obviously would not work when you wanted to design more complex/re-usable objects. However, it made the use of anonymous methods in predicates and simple situations much easier and the delegate syntax was finally resembling some brevity that functional languages enjoy.In summary, our .NET 2.0 - 3.0 solution looks like this:
1 // 1) Declare a delegate
2 delegate int Add(int firstNumber, int secondNumber);
3
4 // 2) Initialize the delegate using an anonymous method(new in .NET 2.0)
5 Add add = delegate(int firstNumber, int secondNumber)
6 {
7 return firstNumber + secondNumber;
8 };
9
10 private void btnAdd_Click(object sender, EventArgs e)
11 {
12 // 3) Retrieve the result
13 int result = add(Convert.ToInt32(this.txtFirst.Text),Convert.ToInt32(this.txtSecond.Text));
.NET 3.5 Solution (Type 1) (C# 3.0)
.NET 3.5 (C# 3.0) introduces even more brevity to delegate syntax with lambda expressions. Lambda expressions are a further evolution of syntax brevity for anonymous methods. You take the spirit of declaring the business logic inline with delegates even further. I broke the .NET 3.5 solution into type 1 and type 2. Type 1 uses just uses the new lambda expression syntax.This solution has several parts:
- Declare the delegate with the desired signature
- Instantiate the delegate (with the business logic defined inside the delegate "body")
- Call the delegate and retrieve the result
Delcare the delegate:
delegate int Add(int firstNumber, int secondNumber);
Instantiate the delegate (with the business logic defined inside the delegate "body")
Add add = (firstNumber, secondNumber) => firstNumber + secondNumber;
In the .NET 2.0 version we had something like this:
Add add = delegate(int firstNumber, int secondNumber)
{
return firstNumber + secondNumber;
};
Our first step in our lambda expression will look like this (This is valid syntax and will compile):
Add add = (int firstNumber, int secondNumber) =>
{
return firstNumber + secondNumber;
};
We didn't really gain much here. We essentially replaced one keyword with another. What we can do next is add additional lambda expression shortcuts. Lambda expressions do not require curly braces or return type.
If we remove both of these, this expression now becomes:
Add add = (int firstNumber, int secondNumber) => firstNumber + secondNumber;
Now the full expression (after removing the two int keywords) remains as just the parameter names:
Add add = (firstNumber, secondNumber) => firstNumber + secondNumber;
Call the delegate and retrieve the result
int result = add(Convert.ToInt32(this.txtFirst.Text),Convert.ToInt32(this.txtSecond.Text));
Summary
We did a lot here. Lambda expressions make a big difference in syntax brevity and clarity. Let's take look what we have now: 1 // 1) Declare a delegate
2 delegate int Add(int firstNumber, int secondNumber);
3
4 // 2) Initialize the delegate using a lambda expressions (new in .NET 3.5)
5 Add add = (firstNumber, secondNumber) => firstNumber + secondNumber;
6
7 private void btnAdd_Click(object sender, EventArgs e)
8 {
9 // 3) Retrieve the result
10 int result = add(Convert.ToInt32(this.txtFirst.Text),Convert.ToInt32(this.txtSecond.Text));
.NET 3.5 Solution (Type 2) (C# 3.0)
The type 2 solutions still utilizes lamdba expressions. However, it also uses the new Func delegate. A Func is a delegate type with several generic parameters defined. There are 5 different Func delegates pre-defined for you with .NET 3.5. Why are there 5 different ones? This should cover you for most of your scenarios when designing methods/delegates, etc. Usually if you are creating signatures of methods with more than 4 parameters, you probably need to refactor your parameters into an object. 'For example, the Func that we need has to have 2 input parameters and a return parameter. This one that is pre-defined in .NET will suit us great:
public delegate TResult Func<T1, T2, TResult>(T1 arg1, T2 arg2);
- Declare & instantiate the delegate (with the business logic defined inside the delegate "body")
- Call the delegate and retrieve the result
Declare & instantiate the delegate (with the business logic defined inside the delegate "body")
We already have a delegate (Func) with the required signature we need. It is a generic delegate; however, we can determine the types of parameters/result we need. All we need to do is create a variable of this type and pass in the expression we created above (.NET 3.5 Type 1). Our entire decleration/instatiation is in ONE line of code now:Func<int, int, int> add = (firstNumber, secondNumber) => firstNumber + secondNumber;
Call the delegate and retrieve the result
int result = add(Convert.ToInt32(this.txtFirst.Text),Convert.ToInt32(this.txtSecond.Text));
Summary
This is as simple as you are going to get right now; a single line of code to declare/initialize a delegate with a lambda expression. 1 Func<int, int, int> add = (firstNumber, secondNumber) => firstNumber + secondNumber;
2
3 private void btnAdd_Click(object sender, EventArgs e)
4 {
5 // 2) Retrieve the result
6 int result = add(Convert.ToInt32(this.txtFirst.Text),Convert.ToInt32(this.txtSecond.Text));
If you want to delve deeper, I would suggest picking up a C# 3 book. I didn't really explain some of the origins of this code and some of the nuances with anonymous methods/lambda expressions or even delegates. I just wanted to focus on the evolution of the C# syntax. Attached is a simple VS 2008 solution zip that demonstrates the 4 different major -- there are lot more smaller permutations -- ways to write delegate syntax. I broke the solution so that each type of code is in its own respective form (this way, every variable is named the same). I also did not want to introduce any other C# constructs but wanted to strictly focus on the topic which was the syntax.
EvolutionOfDelegates.zip (55.06 kb)
No comments:
Post a Comment