Background
This is my fourth article about delegates. I encourage you to read the first three:
More Delegates in .NET
With different versions of .NET Frameworks coming into existence, the usage of delegates becomes more and more desirable. To satisfy more needs, .NET 3.5 introduced the whole new world of delegates. Below I present a table with delegate names and descriptions which come with the .NET 3.5 Framework (.NET 4.0 has even more built-in delegates). In order to be used, a delegate first must be declared. During the declaration, we define a signature of the function that the delegate may represent. By having those predefined delegates, we can skip the declaration part. .NET offeres not only a set of such convenient delegates but has also embedded a Generic data type to many of them. As you can see, many delegates have a Generic type as a parameter or/and return value. This is very convenient. You can substitute the Generic type by any type in your code and the compiler will do the rest.
Delegate |
Description |
Action |
Encapsulates a method that takes no parameters and does not return a value. |
Action<T> |
Encapsulates a method that takes a single parameter and does not return a value. |
Action<T1, T2>> |
Encapsulates a method that has two parameters and does not return a value. |
Action<T1, T2, T3> |
Encapsulates a method that takes three parameters and does not return a value. |
Action<T1, T2, T3, T4> |
Encapsulates a method that has four parameters and does not return a value. |
AppDomainInitializer |
Represents the callback method to invoke when the application domain is initialized. |
AssemblyLoadEventHandler |
Represents the method that handles the AssemblyLoad event of an AppDomain. |
AsyncCallback |
References a method to be called when a corresponding asynchronous operation is completed. |
Comparison<T> |
Represents the method that compares two objects of the same type. |
ConsoleCancelEventHandler |
Represents the method that will handle the CancelKeyPress event of a System.Console . |
Converter<TInput, TOutput> |
Represents a method that converts an object from one type to another. |
CrossAppDomainDelegate |
Used by DoCallBack for cross-application domain calls. |
EventHandler |
Represents the method that will handle an event that has no event data. |
EventHandler<TEventArgs> |
Represents the method that will handle an event. |
Func<TResult> |
Encapsulates a method that has no parameters and returns a value of the type specified by the TResult parameter. |
Func<T, TResult> |
Encapsulates a method that has one parameter and returns a value of the type specified by the TResult parameter. |
Func<T1, T2, TResult> |
Encapsulates a method that has two parameters and returns a value of the type specified by the TResult parameter. |
Func<T1, T2, T3, TResult> |
Encapsulates a method that has three parameters and returns a value of the type specified by the TResult parameter. |
Func<T1, T2, T3, T4, TResult> |
Encapsulates a method that has four parameters and returns a value of the type specified by the TResult parameter. |
Predicate<T> |
Represents the method that defines a set of criteria and determines whether the specified object meets those criteria. |
ResolveEventHandler |
Represents the method that handles the TypeResolve , ResourceResolve , and AssemblyResolve events of an AppDomain. |
UnhandledExceptionEventHandler |
Represents the method that will handle the event raised by an exception that is not handled by the application domain. |
Goal
In this article, I want to discuss different techniques to deal with custom and pre-defined delegates. There are many ways to skin a cat. I personally don't like saying this because I have two cats at home and I love them. But there are many different ways to use a delegate in your program for sure.
We can sort out the delegates from the table:
- Generic type functions and methods
Action
Func
Converter
Comparison
Predicate
- Event handlers
AssemblyLoadEventHandler
ConsoleCancelEventHandler
EventHandler
EventHandler<TEventArgs>
ResolveEventHandler
UnhandledExceptionEventHandler
- Others
AppDomainInitializer
AsyncCallback
(we are already familiar with this delegate - Part2)
CrossAppDomainDelegate
My intention is to talk about the first three delegates Action
, Func
, Converter
. I want to demonstrate how to use these delegates in different ways. Eventually, the compiler would generate pretty similar code, but from a programming stand point, this technique presents great differences.
Action and Func
Action
represents any function that may accept up to four parameters (8 in .NET 4.0) and returns void
. Action<T1,T2>
: T1
and T2
are parameters and can be of any data type. Func
is the same as Action
but it has a return value of any type. Func<T1,T2,TResult>
: T1
, T2
are parameters that can be of any type, TResult
is a returned value. So the only difference between Action
and Func
is the return value. This does not matter for my example. I will do Action
in it, and you can easily transfer this technique onto Func
.
Classic delegate example
public delegate void ShootingHandler(int times);
Here is the class:
public class GunClassicDelegate
{
private ShootingHandler shoot;
public string Name { get; set; }
public GunClassicDelegate(string name)
{
Name = name;
switch (Name.ToLower())
{
case "automatic gun":
shoot = new ShootingHandler(ShootAutomatic);
break;
case "paint gun":
shoot = new ShootingHandler(ShootPaint);
break;
default:
shoot = new ShootingHandler(ShootSingle);
break;
}
}
public void Fire(int times)
{
shoot(times);
}
private void ShootAutomatic(int times)
{
Console.WriteLine("Automatic shooting: ");
for (int i = 0; i < times; i++)
Console.Write("Biff...") ;
Console.WriteLine();
}
private void ShootSingle(int times)
{
Console.WriteLine("Single action shooting: ");
for (int i = 0; i < times; i++)
Console.WriteLine("Bang");
}
private void ShootPaint(int times)
{
Console.WriteLine("Splashing paint ");
for (int i = 0; i < times; i++)
Console.WriteLine("Bloop");
}
}
In the project namespace, I declare my delegate type:
public delegate void ShootingHandler(int times);
Obviously, it returns void
and accepts an integer. In the class GunClassicDelegate
, I have a variable "shoot
" which is of this delegate type.
private ShootingHandler shoot;
During the construction, based on the passed name, I assign to the variable one of the following private functions:
ShootAutomatic
ShootSingle
ShootPaint
As you see, it can be done because those functions match the signature of the delegate. I have a public function Fire
in which the delegate is being invoked. Pure and simple. No problem at all.
Using the pre-built .NET Action delegate
public class GunGenericDelegate
{
private Action<int> shoot;
public string Name { get; set; }
public GunGenericDelegate(string name)
{
Name = name;
switch (Name.ToLower())
{
case "automatic gun":
shoot = ShootAutomatic;
break;
case "paint gun":
shoot = ShootPaint;
break;
default:
shoot = ShootSingle;
break;
}
}
public void Fire(int times)
{
shoot(times);
}
private void ShootAutomatic(int times)
{
Console.WriteLine("Automatic shooting: ");
for (int i = 0; i < times; i++)
Console.Write("Biff...");
Console.WriteLine();
}
private void ShootSingle(int times)
{
Console.WriteLine("Single action shooting: ");
for (int i = 0; i < times; i++)
Console.WriteLine("Bang");
}
private void ShootPaint(int times)
{
Console.WriteLine("Splashing paint ");
for (int i = 0; i < times; i++)
Console.WriteLine("Bloop");
}
}
You can see that there is no need for delegate declaration any more. My private variable shoot
was declared:
private Action<int> shoot;
This tells the compiler that a delegate that returns void
and accepts an integer can be placed into the "shoot
" variable.
The constructor looks simpler. It just assigns the function to the variable:
shoot = ShootAutomatic;
Using the pre-built .NET Action inline delegate
public class GunGenericInLineDelegate
{
public string Name { get; set; }
private Action<int> shoot;
public void Fire(int times) { shoot(times); }
public GunGenericInLineDelegate(string name)
{
Name = name;
switch (Name.ToLower())
{
case "automatic gun":
shoot = delegate(int times)
{
Console.WriteLine("Automatic shooting: ");
for (int i = 0; i < times; i++)
Console.Write("Biff...");
Console.WriteLine();
};
break;
case "paint gun":
shoot = delegate(int times)
{
Console.WriteLine("Splashing paint ");
for (int i = 0; i < times; i++)
Console.WriteLine("Bloop");
};
break;
default:
shoot = delegate(int times)
{
Console.WriteLine("Single action shooting: ");
for (int i = 0; i < times; i++)
Console.WriteLine("Bang");
};
break;
}
}
}
In this example, I decided to dump those three private functions. This simplifies my code a lot. Now the functionality is defined during the constructor. The syntax is simple.
shoot = delegate(int times)
{
The compiler knows that "shoot
" is a delegate type. It allows you to use the "delegate
" keyword and use it as an inline function declaration. Very neat indeed.
Using the pre-built .NET Action inline LAMBDA delegate
public class GunGenericInLineLambda
{
public string Name { get; set; }
public Action<int> shoot;
public void Fire(int times) { shoot(times); }
public GunGenericInLineLambda(string name)
{
Name = name;
switch (Name.ToLower())
{
case "automatic gun":
shoot = (times) =>
{
Console.WriteLine("Automatic shooting: ");
for (int i = 0; i < times; i++)
Console.Write("Biff...");
Console.WriteLine();
};
break;
case "paint gun":
shoot = (times) =>
{
Console.WriteLine("Splashing paint ");
for (int i = 0; i < times; i++)
Console.WriteLine("Bloop");
};
break;
default:
shoot = (times) =>
{
Console.WriteLine("Single action shooting: ");
for (int i = 0; i < times; i++)
Console.WriteLine("Bang");
};
break;
}
}
}
Lambda expressions were introduced to the programming world many years ago, and now C# programmers love to use them when working with delegates.
The logic behind them is the same as in the previous example. The main difference is in declaring functions. Instead of using a delegate function, I use a Lambda expression.
(i) => {
This is a lambda delegate call. (i)
is the list of parameters that the delegate requires. If a delegate does not require parameters, then it will be just (). => {.......}
lambda syntax: go from parameters to the body of the function.
Inside the curly brackets, you can define your functionality using your parameters by name.
And again, because there is no difference between Action
and Func
in implementation, I decided not to create a separate example for Func
.
And here is a program to run:
public class ActionExample
{
static void Main(string[] args)
{
Console.WriteLine("Shooting classic delegate:");
GunClassicDelegate gunClassic1 = new GunClassicDelegate("automatic gun");
GunClassicDelegate gunClassic2 = new GunClassicDelegate("paint gun");
GunClassicDelegate gunClassic3 = new GunClassicDelegate("hand gun");
gunClassic1.Fire(4);
gunClassic2.Fire(4);
gunClassic3.Fire(4);
Console.WriteLine("Shooting generic delegate:");
GunGenericDelegate gunGeneric1 = new GunGenericDelegate("automatic gun");
GunGenericDelegate gunGeneric2 = new GunGenericDelegate("paint gun");
GunGenericDelegate gunGeneric3 = new GunGenericDelegate("hand gun");
gunGeneric1.Fire(4);
gunGeneric2.Fire(4);
gunGeneric3.Fire(4);
Console.WriteLine("Shooting generic inline delegate:");
GunGenericInLineDelegate gunGenericInLine1 =
new GunGenericInLineDelegate("automatic gun");
GunGenericInLineDelegate gunGenericInLine2 =
new GunGenericInLineDelegate("paint gun");
GunGenericInLineDelegate gunGenericInLine3 =
new GunGenericInLineDelegate("hand gun");
gunGenericInLine1.Fire(4);
gunGenericInLine2.Fire(4);
gunGenericInLine3.Fire(4);
Console.WriteLine("Shooting generic lambda delegate:");
GunGenericInLineLambda gunGenericInLineLambda1 =
new GunGenericInLineLambda("automatic gun");
GunGenericInLineLambda gunGenericInLineLambda2 =
new GunGenericInLineLambda("paint gun");
GunGenericInLineLambda gunGenericInLineLambda3 =
new GunGenericInLineLambda("hand gun");
gunGenericInLineLambda1.Fire(4);
gunGenericInLineLambda2.Fire(4);
gunGenericInLineLambda3.Fire(4);
Console.Read();
}
}
When you run it, you will see that there is no difference in the output for every example.
Converter
The delegate Converter<TInput,TOutput>
works in Array.ConvertAll<TInput,TOutput> (TInput [] arraytoconvert, Converter<TInput, TOutput> converter)
. From the first glance, it seems to be kind of complicated, but it is very simple. If you want to convert an array of objects from one data type to another, you need to use the generic function of the Array
object ConvertAll
. This function accepts two parameters. The first parameter is an array of the data type you want to convert. The second parameter is a delegate to the function that does the conversion. This delegate is our Converter
delegate that was so conveniently prepared for you by .NET creators.
This is my code explaining the processor:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Person(string firstname, string lastname)
{
FirstName = firstname;
LastName = lastname;
}
}
public class PersonInfo
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName { get { return FirstName + " " + LastName; } }
public static PersonInfo ConvertToPersonInfo(Person person)
{
PersonInfo res = new PersonInfo();
res.FirstName = person.FirstName;
res.LastName = person.LastName;
return res;
}
}
I have two classes, Person
and PersonInfo
. There is only a small difference between them, but for a compiler, they are two different classes. If I want to convert an array of Person
class to an array of PersonInfo
class, I have to use the Converter
delegate. So I created a static function ConvertToPersonInfo
, which accepts an object of Person
type and returns an object of PersonInfo
type. This function belongs to the PersonInfo
class. In the following code examples, I will display three different Main
methods. In the code, they have different names: MainClassic
, MainLambda
, MainDelegate
. You will have to choose one of them for your program. They demonstrate different delegates' techniques.
MainClassic
references to the PersonInfo.ConvertToPersonInfo
function that was pre-created in the PersonInfo
class. The last two Main
methods describe the conversion inline, without pre-creating a special function for this operation. And, as you can see, you can use Lambda syntax for the delegate as well.
class Program {
static void MainClassic(string[] args)
{
Person[] people = { new Person("ed", "g"),
new Person("al", "g") };
PersonInfo[] newpeople = Array.ConvertAll(people,
new Converter<Person,PersonInfo>(PersonInfo.ConvertToPersonInfo));
}
static void MainLambda(string[] args)
{
Person[] people = { new Person("ed", "g"),
new Person("al", "g") };
PersonInfo[] newpeople = Array.ConvertAll(people, (p) => {
PersonInfo pi = new PersonInfo();
pi.LastName = p.LastName;
pi.FirstName = p.FirstName;
return pi;
});
}
static void MainDelegate(string[] args)
{
Person[] people = { new Person("ed", "g"),
new Person("al", "g") };
PersonInfo[] newpeople = Array.ConvertAll(people, delegate(Person p)
{
PersonInfo pi = new PersonInfo();
pi.LastName = p.LastName;
pi.FirstName = p.FirstName;
return pi;
});
}
}
And again, I hope you realized that those methods cannot work simultaneously; you have to rename them to Main
and run them one by one in your Console application. They all have created an array of two Person
type objects. MainClassic
creates a new delegate in a classic style with a new
keyword and assigns the PersonInfo.ConvertToPersonInfo
function to the delegate. MainDelegate
uses inline delegate creation, when MainLambda
is also inline but prefers to use Lambda syntax for using the delegate.
Conclusions
There is more for the topic of delegates, but I would like to stop it here. I just wanted to give you an idea. As you can see, you have many different options for how you want to utilize the delegates usage in your code. If there are no suitable predefined delegates, you can declare your own and use them. You can take advantage from inline coding rather than creating named functions and assigning them to the delegates. You can use lambda syntax, which is extremely convenient for delegates. This is the last part in my series about delegates. Please vote for the article if you liked it.