Click here to Skip to main content
15,879,326 members
Articles / Programming Languages / C# 3.5

Delegates in C# - Attempt to look inside. Part 4

Rate me:
Please Sign up or sign in to vote.
4.84/5 (67 votes)
20 Oct 2010CPOL8 min read 83.5K   118   20
Classic, generic, lambda. A different code technique.

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.

DelegateDescription
ActionEncapsulates 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.
AppDomainInitializerRepresents the callback method to invoke when the application domain is initialized.
AssemblyLoadEventHandlerRepresents the method that handles the AssemblyLoad event of an AppDomain.
AsyncCallbackReferences 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.
ConsoleCancelEventHandlerRepresents 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.
CrossAppDomainDelegateUsed by DoCallBack for cross-application domain calls.
EventHandlerRepresents 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.
ResolveEventHandlerRepresents the method that handles the TypeResolve, ResourceResolve, and AssemblyResolve events of an AppDomain.
UnhandledExceptionEventHandlerRepresents 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:

  1. Generic type functions and methods
    • Action
    • Func
    • Converter
    • Comparison
    • Predicate
  2. Event handlers
    • AssemblyLoadEventHandler
    • ConsoleCancelEventHandler
    • EventHandler
    • EventHandler<TEventArgs>
    • ResolveEventHandler
    • UnhandledExceptionEventHandler
  3. 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

C#
public delegate void ShootingHandler(int times);

Here is the class:

C#
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:

C#
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.

C#
private ShootingHandler shoot;

During the construction, based on the passed name, I assign to the variable one of the following private functions:

  1. ShootAutomatic
  2. ShootSingle
  3. 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

C#
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:

C#
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:

C#
shoot = ShootAutomatic;

Using the pre-built .NET Action inline delegate

C#
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.

C#
shoot = delegate(int times)
{ //the functionality goes here };

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

C#
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.

C#
(i) => { //functionality goes here}

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:

C#
public class ActionExample
{
    static void Main(string[] args)
    { 
        //part - classic:
        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);

        //part - generic delegate:
        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);

        //part - generic inline delegate:
        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);

        //part - generic lambda delegate:
        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:

C#
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.

C++
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
United States United States
I started as an electronic engineer and moved into programming in 1989, was fascinated by Internet and developed my first web application in 1997.
When Microsoft introduced C# I became its fan and am not disappointed by now.
As for the hobbies, except of course reading, I love skiing and horse riding.

Comments and Discussions

 
QuestionExpertly taught important knowledge Pin
hutz22-Oct-13 11:00
hutz22-Oct-13 11:00 
GeneralMy vote of 5 Pin
phil.o21-Sep-13 21:45
professionalphil.o21-Sep-13 21:45 
GeneralMy vote of 5 Pin
ssathy6225-Feb-13 6:26
ssathy6225-Feb-13 6:26 
GeneralMy vote of 5 Pin
Sponaris22-Sep-12 5:23
Sponaris22-Sep-12 5:23 
GeneralMy vote of 4 Pin
persistence91130-Aug-12 4:15
persistence91130-Aug-12 4:15 
GeneralMy vote of 5 Pin
Kasyapa25-Nov-10 11:07
Kasyapa25-Nov-10 11:07 
GeneralMy vote of 5 Pin
ranjan_namitaputra18-Nov-10 9:57
ranjan_namitaputra18-Nov-10 9:57 
GeneralMy vote of 5 Pin
justcsprogrammer15-Nov-10 5:28
justcsprogrammer15-Nov-10 5:28 
GeneralMy vote of 2 Pin
DenisK13-Nov-10 10:11
professionalDenisK13-Nov-10 10:11 
GeneralMy vote of 5 Pin
Rick Dean10-Nov-10 14:57
Rick Dean10-Nov-10 14:57 
GeneralMy vote of 5 Pin
Abhinav S7-Nov-10 5:58
Abhinav S7-Nov-10 5:58 
GeneralMy vote of 4 Pin
NirdeshSaini27-Oct-10 17:12
NirdeshSaini27-Oct-10 17:12 
GeneralMy vote of 5 Pin
igetorix26-Oct-10 10:27
igetorix26-Oct-10 10:27 
GeneralMy vote of 5 Pin
Eric Xue (brokensnow)21-Oct-10 11:17
Eric Xue (brokensnow)21-Oct-10 11:17 
GeneralMy vote of 4 Pin
Sivaraman Dhamodharan21-Oct-10 3:28
Sivaraman Dhamodharan21-Oct-10 3:28 
GeneralGreat Tretment of Imporant Topic Pin
dpminusa18-Oct-10 10:33
dpminusa18-Oct-10 10:33 
GeneralMy vote of 5 Pin
ChrisBraum15-Oct-10 10:33
ChrisBraum15-Oct-10 10:33 
GeneralMy vote of 5 Pin
Brian Pendleton13-Oct-10 8:16
Brian Pendleton13-Oct-10 8:16 
GeneralVote of 5. Pin
Kelvin Armstrong13-Oct-10 0:31
Kelvin Armstrong13-Oct-10 0:31 
GeneralRe: Vote of 5. Pin
Ed Guzman13-Oct-10 8:12
Ed Guzman13-Oct-10 8:12 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.