Fundamentals of Delegates

Introduction

As we saw from previous lessons, a function is a section of code that takes care of a job. The role of a function can be used or revealed by calling the function. Consider an example of a function as follows:

void Create()
{
    Console.WriteLine("Actions speak louder than words.");
}

To use such a function, you must explicitly call it. Here is an example:

static void Create()
{
    Console.WriteLine("Actions speak louder than words.");
}

Create();

This would produce:

Actions speak louder than words

Press any key to close this window . . .

In the strict sense and in the tradition of the C++ language, a function is created indipendently of any class so that the function can be called anytime, without going through a class. This concept of funcions is supported in the .NET library with the name of delegate and made available to C# (all other .NET languages, such as Visual Basic, F#, and other languages such as C++, PHP, Python, JavaScript, TypeScript, etc, support the concepts of functions).

Practical LearningPractical Learning: Introducing Delegates

  1. Start Microsoft Visual Studio and create a new Console App (that supports the .NET 8.0 (Long-Term Support) named PayrollPreparation06
  2. In the Solution Explorer, right-click Program.cs -> Rename
  3. Type PayrollEvaluation (to get PayrollEvaluation.cs)
  4. Press Enter

Introduction to Creating a Delegate

So far in our lessons, we have studied various types that can be created from scratch. They are classes, records, structures, and enumerations. The delegate follows the same trend. This means that, like a class, a record, a structure, or an enumeration, a delegate can be created in its own file or in a file that also contains other types. To create a delegate in its own file, on the main menu, click Project -> Add New Item... Or, in the Solution Explorer, right-click the name of the project -> Add -> New Item... In the Add New Item dialog box, click Code File. Accept the default name of the file or change it. Click Add. In the document, type the necessary code. Remember that you can also create a delegate in a file that contains (a) class(es) and/or (an) enumeration(s).

The basic formula to create a delegate is:

[options] [modifier(s)] delegate void delegate-name ([formal-parameters]);

You can start with some opions. The modifiers can be one or an appropriate combination of the following keywords: new, public, private, protected, or internal. The delegate keyword is required. A delegate can be any of the types we have used so far. It can also be of type void. The delegate-name must be a valid C# name. The names of delegates follow the rules and suggestions of names of functions.

Because a delegate is some type of a template for a function or method, you must use parentheses, required for every function or method. If the function will not take any argument, leave the parentheses empty. Here is an example:

delegate string Observation();

A delegate is primarily a syntax for a function, but it is used to refer to an actual function. To use a delegate, you must define a function that would perform the actual job. That function must have the same return type and the same (number of) argument(s), if any. Here is an example:

delegate void Observation();

void Create()
{

}

ApplicationPractical Learning: Creating a Delegate

Associating a Function to a Delegate

After creating the function, you can associate it to the name of the desired delegate. To do that, where you want to use the function, declare a variable of the type of the delegate. You have two options:

Practical LearningPractical Learning: Creating a Function for a Delegate

Accessing a Delegate

Once you have associated a function to a delegate variable, you can use the delegate variable as if it were a defined function. That is, you can call it as you would proceed for a normal function. Here is an example:

Observation quote = new Observation(Create);

void Create()
{
    Console.WriteLine("A man with one watch knows what time it is; " +
                      "a man with two watches is never quite sure. - Lee Segall -");
}

quote();

delegate void Observation();

This would produce:

A man with one watch knows what time it is; a man with two watches is never quite sure. - Lee Segall -

Press any key to close this window . . .

ApplicationPractical Learning: Accessing a Delegate

  1. Change the document as follows:
    using static System.Console;
            
    void Estimate()
    {
        double taxes = 0.00;
        string residence = "";
    
        WriteLine("Amazing DeltaX - State Income Tax");
        WriteLine("===========================================");
        WriteLine("States");
        WriteLine("NN - None");
        WriteLine("UT - Utah");
        WriteLine("TX - Texas");
        WriteLine("AK - Alaska");
        WriteLine("NV - Nevada");
        WriteLine("FL - Florida");
        WriteLine("IN - Indiana");
        WriteLine("WY - Wyoming");
        WriteLine("IL - Illinois");
        WriteLine("MI - Michigan");
        WriteLine("CO - Colorado");
        WriteLine("TN - Tennessee");
        WriteLine("WA - Washington");
        WriteLine("PA - Pennsylvania");
        WriteLine("SD - South Dakota");
        WriteLine("NH - New Hampshire");
        WriteLine("NC - North Carolina");
        WriteLine("-------------------------------------------");
        Write("Type the 2-letter state: ");
        string state = ReadLine()!!;
        Write("Gross Salary:            ");
        double salary = Convert.ToDouble(ReadLine()!!);
    
        switch (state.ToUpper())
        {
            case null:
                taxes = 0.00;
                residence = "No Income Tax Collected";
                break;
            case "AK":
            case "FL":
            case "NH":
            case "NV":
            case "SD":
            case "TN":
            case "TX":
            case "WA":
            case "WY":
                taxes = 0.00;
                residence = "Income Tax Not Collected";
                break;
            case "PA":
                taxes = salary * 3.07 / 100.00;
                residence = "Pennsylvania (3.07%)";
                break;
            case "IN":
                taxes = salary * 3.30 / 100.00;
                residence = "Indiana (3.3%)";
                break;
            case "IL":
                taxes = salary * 3.75 / 100.00;
                residence = "Illinois (3.75%)";
                break;
            case "MI":
                taxes = salary * 4.25 / 100.00;
                residence = "Michigan (4.25%)";
                break;
            case "CO":
                taxes = salary * 4.63 / 100.00;
                residence = "Colorado (4.63%)";
                break;
            case "UT":
                taxes = salary * 5 / 100.00;
                residence = "Utah (5%)";
                break;
            case "NC":
                taxes = salary * 5.75 / 100.00;
                residence = "North Carolina (5.75%)";
                break;
            default:
                taxes = 0.00;
                residence = "Default";
                break;
        }
    
        WriteLine("Amazing DeltaX - State Income Tax");
        WriteLine("===========================================");
        WriteLine("Tax Preparation Summary");
        WriteLine("-------------------------------------------");
        WriteLine("Gross Salary:       {0}", salary.ToString("F"));
        WriteLine("Employee Residence: {0}", residence);
        WriteLine("State Income Tax:   {0}", taxes.ToString("F"));
        WriteLine("Net Pay:            {0}", (salary - taxes).ToString("F"));
        WriteLine("-------------------------------------------");
        WriteLine("===========================================");
    }
    
    Summarize sum = new Summarize(Estimate);
    
    sum();
    
    delegate void Summarize();
  2. To execute the project, on the main menu, click Debug -> Start Without Debugging
  3. For the state, type the two letters for Pennsylvania and press Enter
  4. For the Gross Salary, type a number such as 3066.48
    Amazing DeltaX - State Income Tax
    ===========================================
    States
    NN - None
    UT - Utah
    TX - Texas
    AK - Alaska
    NV - Nevada
    FL - Florida
    IN - Indiana
    WY - Wyoming
    IL - Illinois
    MI - Michigan
    CO - Colorado
    TN - Tennessee
    WA - Washington
    PA - Pennsylvania
    SD - South Dakota
    NH - New Hampshire
    NC - North Carolina
    -------------------------------------------
    Type the 2-letter state: pa
    Gross Salary:            3066.48
  5. Press Enter:
    Amazing DeltaX - State Income Tax
    ===========================================
    States
    NN - None
    UT - Utah
    TX - Texas
    AK - Alaska
    NV - Nevada
    FL - Florida
    IN - Indiana
    WY - Wyoming
    IL - Illinois
    MI - Michigan
    CO - Colorado
    TN - Tennessee
    WA - Washington
    PA - Pennsylvania
    SD - South Dakota
    NH - New Hampshire
    NC - North Carolina
    -------------------------------------------
    Type the 2-letter state: pa
    Gross Salary:            3066.48
    Amazing DeltaX - State Income Tax
    ===========================================
    Tax Preparation Summary
    -------------------------------------------
    Gross Salary:       3066.48
    Employee Residence: Pennsylvania (3.07%)
    State Income Tax:   94.14
    Net Pay:            2972.34
    -------------------------------------------
    ===========================================
    
    Press any key to close this window . . .
  6. Press A to close the window and return to your programming environment

Delegates and Parameters

Introduction

If you want to use a function that takes arguments and associate it to a delegate, when declaring the delegate, provide the necessary parameter(s) in its parentheses. Here is an example of a delegate that takes a parameter:

delegate void Simple(string arg);

When defining the associated method, make it take the same number and type(s) of parameter(s). Here is an example:

void Create(string msg)
{
    Console.WriteLine(arg);
}

To associate the function to the delegate, declare a variable for the delegate and initialize it. You can use the new operator and the name of the delegate

void Create(string msg)
{
    Console.WriteLine(msg);
}

Observation quote = new Observation(Create);

delegate void Observation(string arg);

To simplify your code, you can simply assign the name of the function to the delegate variable. Here is an example:

Observation quote = Create;

To actually use the delegate, when calling it, add the parentheses to it and in the parentheses, provide a value for the argument. Here is an example:

void Create(string msg)
{
    Console.WriteLine(msg);
}

string str = "Chemistry is the study of matter. Matter is the building " +
             "block of anything in the environment, that is, anything " +
             "that occupies any form of space, anything that can be " +
             "seen or is hidden to the human eye.";

Observation quote = new Observation(Create);

// Accessing the delegate
quote(str);

delegate void Observation(string arg);

This would produce:

Chemistry is the study of matter. Matter is the building block of anything in the environment, that is, anything that occupies any form of space, anything that can be seen or is hidden to the human eye.

Press any key to close this window . . .

Remember that you have various options to declare the variable. Here are examples:

void Create(string msg)
{
    Console.WriteLine(msg);
    Console.WriteLine("-----------------------------------------------------------------------");
}

// Regular variable declaration
Observation quote1 = new Observation(Create);
// Variable declared with the "var" or the "dynamic" keyword
var quote2 = new Observation(Create);
// Variable declared with the "new" operator only
Observation quote3 = new(Create);

string str = "Chemistry is the study of matter. Matter is the building " +
             "block of anything in the environment, that is, anything " +
             "that occupies any form of space, anything that can be " +
             "seen or is hidden to the human eye.";

quote1(str);

str = "A man with one watch knows what time it is; " +
      "a man with two watches is never quite sure. - Lee Segall -";

quote2(str);

str = "Never let your head hang down. Never give up and sit down and grieve. " +
      "Find another way. And don't pray when it rains if you don't pray when " +
      "the sun shines. - Richard M Nixon";

quote3(str);

delegate void Observation(string arg);

This would produce:

Chemistry is the study of matter. Matter is the building block of anything in the environment, that is, anything that occupies any form of space, anything that can be seen or is hidden to the human eye.
-----------------------------------------------------------------------
A man with one watch knows what time it is; a man with two watches is never quite sure. - Lee Segall -
-----------------------------------------------------------------------
Never let your head hang down. Never give up and sit down and grieve. Find another way. And don't pray when it rains if you don't pray when the sun shines. - Richard M Nixon
-----------------------------------------------------------------------

Press any key to close this window . . .

In the above example, we used a string argument. In the same way, for the parameter, you can use any of the types we have used so far.

A Delegate with Many Parameters

A delegate can use more than one parameter. When creating the delegate, pass the same number and type(s) of parameter(s). Here is an example:

delegate void Operation(double x, string op, double y);

When calling the delegate from your variable, pass the appropriate number and type(s) of argument(s):

using static System.Console;

Write("Number 1: ");
double operand1 = double.Parse(ReadLine()!);
Write("Operator: ");
string oper = ReadLine()!;
Write("Number 2: ");
double operand2 = double.Parse(ReadLine()!);

Operation exam = delegate (double number1, string oper, double number2)
{
    double result = 0;

    switch (oper)
    {
        case "+":
            result = number1 + number2;
            break;

        case "-":
            result = number1 - number2;
            break;

        case "*":
            result = number1 * number2;
            break;

        case "/":
            if (number2 != 0)
                result = number1 / number2;
            break;
    }

    WriteLine("Result: " + result.ToString());
};

exam(operand1, oper, operand2);

delegate void Operation(double x, string op, double y);

Primary Characteristics of Delegates

A Static Function for a Delegate

You can associate a delegate to a static function. In this case, to access the function, you must qualify it from its class. Here is an example:

static void Create()
{
    Console.WriteLine("A man with one watch knows what time it is; " +
              "a man with two watches is never quite sure. - Lee Segall -");
}

Observation obs = new Observation(Create);

obs();

delegate void Observation();

Delegates and Namespaces

As mentioned previously, you can create a delegate in its own file. If necessary, to reduce name conflict, you can create a delegate in a namespace. In this case, to access the delegate outside of its namespace, you must qualify the delegate. To do this, type the name of the namespace, a period, and the name of the delegate. Here is an example:

void Create()
{
    Console.WriteLine("A man with one watch knows what time it is; " +
                      "a man with two watches is never quite sure. - Lee Segall -");
}

Knowledge.Observation obs = new Knowledge.Observation(Create);

obs();

namespace Knowledge
{
    delegate void Observation();
}

Lambda Expressions

Introduction

We have already seen that the C# language supports the idea of nesting a function inside another function or inside a method. Here is an example:

Create();

// Optional other code here

void Create()
{
    Console.WriteLine("A man with one watch knows what time it is; " +
                      "a man with two watches is never quite sure. - Lee Segall -");
}

A nested function can be associated with a delegate, exactly as we have done so far. After creating such a function, you can call it in the nesting code, before or after the code of the function. Here is an example:

Observation obs = new Observation(Create);

obs();

void Create()
{
    Console.WriteLine("A man with one watch knows what time it is; " +
                      "a man with two watches is never quite sure. - Lee Segall -");
}

delegate void Observation();

An Anonymous Delegate

In the previous examples, we had to create a function that would be associated with a delegate. In the same way, you can create a type of local implementation of a function (in an existing function). In other words, you don't have to define a formal function that would initialize the delegate. Such a function is referred to as anonymous.

Before implementing an anonymous function, first declare the delegate you will use, as we did previously. To create an anonymous function, declare a variable for the delegate and assign it the delegate keyword as if it were a function. That is, followed by parentheses and curly brackets that would represent the body of the function. In the body of the anonymous function or method, do whatever you want. Here is an example:

Observation obs = delegate ()
{
    Console.WriteLine("A man with one watch knows what time it is; " +
                      "a man with two watches is never quite sure. - Lee Segall -");
};
        
delegate void Observation();

Once you have done this, you can then call the delegate variable as if it were a normal function. Here is an example:

Observation obs = delegate ()
{
    Console.WriteLine("A man with one watch knows what time it is; " +
                      "a man with two watches is never quite sure. - Lee Segall -");
};
            
obs();

delegate void Observation();

The Lambda Operator

You can also create an anonymous function using an operator called lambda and represented by =>. From our example above, to use the lambda operator to create an anonymous method, omit the delegate keyword and follow the parentheses by the operator. Here is an example:

Observation obs = () =>
{
    Console.WriteLine("A man with one watch knows what time it is; " +
                      "a man with two watches is never quite sure. - Lee Segall -");
};

obs();

delegate void Observation();

Parameterized Delegates and Lambda Expressions

You can create an anonymous method for a delegate that takes one or more parameters. You can do this using the delegate keyword. In its parentheses, pass an argument that is the same type as the argument of the delegate. Then, in the body of the method, you can use or ignore the parameter. When calling the variable of the delegate, use the same rules we have applied so far. Here is an example:

Observation obs = delegate(string x)
{
    Console.WriteLine(x);
};

string msg = "A man with one watch knows what time it is; " +
             "a man with two watches is never quite sure. - Lee Segall -";

obs(msg);

delegate void Observation(string arg);

Instead of the delegate keyword, you can define an anonymous method using the lambda operator. In this case, in the parentheses of the lambda expression, enter the data type of the argument followed by its name. In the body of the anonymous method, use the argument or ignore it as you see fit. Here is an example:

Observation obs = (string x) =>
{
    Console.WriteLine(x);
};

string msg = "A man with one watch knows what time it is; " +
             "a man with two watches is never quite sure. - Lee Segall -";

obs(msg);

delegate void Observation(string arg);

In our example, we specified the type of the parameter. If you want, you can let the compiler figure out the type of argument. In this case, pass only the name of the parameter and not its type. Here is an example:

Observation obs = (x) =>
{
    Console..WriteLine(x);
};

string msg = "A man with one watch knows what time it is; " +
             "a man with two watches is never quite sure. - Lee Segall -";

obs(msg);
        
delegate void Observation(string arg);

In the same way, you can use a lambda expression to create an anonymous function that uses many parameters. You can implement it with the delegate keyword. Here is an example:

using static System.Console;

Write("Number 1: ");
double operand1 = double.Parse(ReadLine()!);
Write("Operator: ");
string oper = ReadLine()!;
Write("Number 2: ");
double operand2 = double.Parse(ReadLine()!);

Operation exam = delegate(double number1, string oper, double number2)
{
    double result = 0;

    switch (oper)
    {
        case "+":
            result = number1 + number2;
            break;

        case "-":
            result = number1 - number2;
            break;

        case "*":
            result = number1 * number2;
            break;

        case "/":
            if (number2 != 0)
                result = number1 / number2;
            break;
    }

    WriteLine("Result: " + result.ToString());
};

exam(operand1, oper, operand2);

delegate void Operation(double x, string op, double y);

You can omit using the delegate keyword and use the => operator instead. Here is an example:

Console.Write("Number 1: ");
double operand1 = double.Parse(Console.ReadLine()!);
Console.Write("Operator: ");
string oper = Console.ReadLine()!;
Console.Write("Number 2: ");
double operand2 = double.Parse(Console.ReadLine()!);

Operation exam = (double number1, string oper, double number2) =>
{
    double result = 0;

    switch (op)
    {
        case "+":
            result = number1 + number2;
            break;

        case "-":
            result = number1 - number2;
            break;

        case "*":
            result = number1 * number2;
            break;

        case "/":
            if (number2 != 0)
                result = number1 / number2;
            break;
    }

    Console.WriteLine("Result: " + result.ToString());
};

exam(operand1, oper, operand2);
delegate void Operation(double x, string op, double y);

Once again, the data types of the parameters are optional.

A Delegate as a Parameter

Passing a Function as Argument

By default, in C#, you cannot pass a function as argument (although some other languages support that concept). Delegates provide an alternative. Because a delegate is an object, or rather treated as such, it can be used as a parameter.

To start, you must have or create a delegate that has the syntax of the function you want to use. Here is an example:

delegate void Observation();

Once you have the delegate, you can create a function with the same syntax but that performs the action you want. Here is an example:

delegate void Observation();

void Hold()
{
           
}

After doing this, you can create a function to which you would pass the delegate as parameter. To do this, specify the name of the delegate as the data type of the parameter and add a name for the parameter. Here is an example:

void Hold()
{
           
}

void Create(Observation speech)
{
}

delegate void Observation();

In the body of the function, call the delegate as you would call a function. Here is an example:

void Create(Observation speech)
{
    speech();
}

delegate void Observation();

When you call the function that takes a delegate as parameter, pass the name of the function that is associated to the delegate. This can be done in a class as follows:

Console.WriteLine("A man with one watch knows what time it is; a man with two watches is never quite sure. - Lee Segall -");

void Hold()
{
           
}

void Create(Observation speech)
{
    speech();
}

Create(Hold);
        
delegate void Observation();

In the body of the function that takes a delegate as parameter, we simply called the parameter as a function. In some cases, you will want or need to initialize the argument. To do that, use the new operation to assign the delegate to the parameter as done when declaring a variable of a class. In the parentheses of the delegate, pass the name of the function that actually performs the desired operation. Here is an example:

void Hold()
{            
}
        
void Create(Observation speech)
{
    speech = new Observation(Hold);

    speech();
}

Create(Hold);
        
delegate void Observation();

Remember that if you have a function that is called either once or by only another function, you can nest such a function in the body of the only function that needs its service. Here is an example:

void Hold()
{
    Console.WriteLine("A man with one watch knows what time it is; a man " +
                      "with two watches is never quite sure. - Lee Segall -");
}
        
Create(Hold);
        
void Create(Observation speech)
{
    speech();
}        

delegate void Observation();

A Function as Parameter in a Lambda Expression

Instead of formally defining the desired behavior in the body of the function that would be associated with a delegate passed as parameter, you can create that function simply as an empty placeholder. Then use a lambda expression to define the behavior you want for the function. In this case, in the body of the function that takes the delegate as parameter, initialize the parameter with the delegate keyword and parentheses. Then create a curly-delimited body in which you can perform the operation(s) you want. Here is an example:

void Hold()
{
}
        
void Create(Observation speech)
{
    speech = delegate ()
    {
        Console.WriteLine("A man with one watch knows what time it is; a man " +
                  "with two watches is never quite sure. - Lee Segall -");
    };

    speech();
}

Create(Hold);

delegate void Observation();

Speaking of the delegate keyword, remember that you can omit it and use the => operator instead. Here is an example:

void Hold()
{
}
        
void Create(Observation speech)
{
    speech = () =>
    {
        Console.WriteLine("A man with one watch knows what time it is; a man " +
                  "with two watches is never quite sure. - Lee Segall -");
    };

    speech();
}

Create(Hold);

delegate void Observation();

Speaking of the function used as an empty placeholder, you can simply create in the section that needs it. This can be done as follows:

void Create(Observation speech)
{
    speech = delegate()
    {
        Console.WriteLine("A man with one watch knows what time it is; a man " +
                  "with two watches is never quite sure. - Lee Segall -");
    };

    speech();
}

Create(Hold);

void Hold() {}

delegate void Observation();

Or like this:

void Create(Observation speech)
{
    speech = () =>
    {
        Console.WriteLine("A man with one watch knows what time it is; a man " +
                  "with two watches is never quite sure. - Lee Segall -");
    };

    speech();
}

Create(Hold);

void Hold() {}

delegate void Observation();

A Delegate with a Parameter

We have already seen that you can create a delegate that takes a parameter. Here is an example of a delegate that takes a string as parameter:

delegate void Observation(string x);

Such a delegate can be passed as parameter to a function. To start, create a function that uses the same syntax as the delegate and that performs the action you want. Here is an example:

void Hold(string msg)
{
    Console.WriteLine(msg, "Citations");
}

delegate void Observation(string x);

Create a function that takes the delegate as patameter. In the body of the function, call the parameter as a function but pass an argument that is the same type the delegate takes. Then, when calling the function that takes the delegate as parameter, pass the name of the delegate-associated function as argument. Here is an example:

using static System.Console;

void Hold(string msg)
{
    WriteLine(msg);
}
        
void Create(Observation speech)
{
    speech("A man with one watch knows what time it is; a man " +
               "with two watches is never quite sure. - Lee Segall -");
}

Create(Hold);

delegate void Observation(string x);

A Delegate with Parameters

You can create a delegate that takes more than one parameter. Follow the above steps to pass the delegate as argument.

A Method as Parameter in a Lambda Expression

Instead of creating a formal method that would be associated with a delegate passed as parameter, you can define a lambda expression in the place where the delegate variable is passed as argument.

Delegates Compositions

Adding Delegates

One of the characteristics that set delegates apart from C/C++ function pointers is that a delegate can be added to another using the + operator. This is referred to as composition. This is done by adding one delegate variable to another as in "result = a + b". After doing this, to access the action produced, call the variable as if it were a function, as in "result()". In the same way, you can add many delegates as in "result = a + b + c". or more. You can call the variable like a function as in "result()".

Delegates and Compound Addition

Besides the regular addition, delegates also support the compound addition performed using the += operator. It works exactly as in C# arithmetic. This means that you can first add a delegate to a variable and assign the result to the variable as follows:

result = first;
result = result + second;

As an alternative, you can use the += operator between the variable and the right operand.

Introduction to Delegates and Classes

Introduction to Delegates and Methods

Everything we learn about a function is also applicable to a method as long as we keep in mind that a method is a member of a class.

A delegate can be associated with a method of a class. If the delegate is created outside the class, the method must be accessible; that is, the method should be created as public or internal. This time, to access the method and associate it to a delegate, you have to first declare a variable of that class. Here is an example:

Sociology soc = new Sociology();
Observation obs = new Observation(soc.Create);

obs();

delegate void Observation();

public class Sociology
{
    public void Create()
    {
        Console.WriteLine("A man with one watch knows what time it is; " +
                        "a man with two watches is never quite sure. - Lee Segall -",
                        "Citations");
    }
}

Delegates and Classes

So far, we involved only primitive types with delegates, especially when it came to parameters. Indeed, any type of value can be passed to a delegate and subsequently to its associated method(s). You can use any appropriate class. For example you can create your own class, but as you know it already, the .NET library provides a rich library of classes you can use.

ApplicationPractical Learning: Ending the Lesson


Previous Copyright © 2008-2024, FunctionX Wednesday 20 October 2021 Next