Fundamentals of Delegates

Introduction

Consider an example of a function as follows:

@using static System.Console

@functions{
    void Create()
    {
        WriteLine("Actions speak louder than words");
    }
}

Throughout our lessons, we refer to a function as a section of code that solves a specific problem. 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. Therefore, to use a function like the above, you can (must) call it. Here is an example:

@page
@model Exercises.Pages.ExerciseModel
@using static System.Console
@functions{
    void Create()
    {
        WriteLine("Actions speak louder than words.");
    }
}

@{
    Create();
}

This would produce:

Actions speak louder than words.

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# (and the other .NET languages).

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.

The basic formula to create a delegate is:

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

You can start with some options. 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 a 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. You can create a delegate in a C# code file. You can create a delegate in a model page. Here is an example:

using Microsoft.AspNetCore.Mvc.RazorPages;

namespace Exercises.Pages
{
    delegate void Observation();

    public class ExerciseModel : PageModel
    {
        public void OnGet()
        {
        }
    }
}

You can also create a delegate in a razor page (we will see an example next).

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:

@page
@model Exercises.Pages.ExerciseModel
@using static System.Console
@{
}

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

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:

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:

@page
@model Exercises.Pages.ExerciseModel
@using static System.Console
@{
    Observation quote = new Observation(Create);

    quote();
}

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

This would produce:

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

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:

@functions{
    delegate void Simple(string arg);
}

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

@using static System.Console

@functions{
    void Create(string msg)
    {
        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 Here is an example:

@using static System.Console

@{
    Observation quote = new Observation(Create);
}

@functions{
    delegate void Observation(string arg);

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

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:

@page
@model Exercises.Pages.ExerciseModel
@using static System.Console
@{
    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);

    quote(str);
}

@functions{
    delegate void Observation(string arg);

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

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.

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:

functions@{
    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).

Primary Characteristics of Delegates

A Static Function for a Delegate

You can associate a delegate to a static function. You can access the function the same way we have done so far. Here is an example:

@page
@model Exercises.Pages.ExerciseModel
@using static System.Console
@{
    Observation quote = new Observation(Create);

    quote();
}

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

In the samw way, you can create a static method to be associated to a delegate. In that case, when accessing the method, you must qualify it from its class.

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. Here is an example:

using Microsoft.AspNetCore.Mvc.RazorPages;

namespace Exercises.Pages
{
    public class ExerciseModel : PageModel
    {
        public void OnGet()
        {
        }
    }
}

namespace Knowledge
{
    delegate void Observation();
}

To access the delegate outside 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:

@page
@model Exercises.Pages.ExerciseModel
@using static System.Console
@{
    Knowledge.Observation obs = new Knowledge.Observation(Create);
    
    obs();
}

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

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:

@page
@model Exercises.Pages.ExerciseModel
@using static System.Console
@{
    Process();
}

@functions{
    void Process()
    {
        Create();

        // Optional other code here
        
        void Create()
        {
            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:

@page
@model Exercises.Pages.ExerciseModel
@using static System.Console
@{
    Process();
}

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

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:

@page
@model Exercises.Pages.ExerciseModel
@using static System.Console
@{
    Observation obs = delegate ()
    {
        WriteLine("A man with one watch knows what time it is; " +
                  "a man with two watches is never quite sure. - Lee Segall -");
    };
}

@functions{
    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:

@page
@model Exercises.Pages.ExerciseModel
@using static System.Console
@{
    Observation obs = delegate ()
    {
        WriteLine("A man with one watch knows what time it is; " +
                  "a man with two watches is never quite sure. - Lee Segall -");
    };
            
    obs();
}

@functions{
    delegate void Observation();
}

The Lambda Operator

You can also create an anonymous function using an operator called lambda. It is represented as =>. 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:

@page
@model Exercises.Pages.ExerciseModel
@using static System.Console
@{
    Observation obs = () =>
    {
        WriteLine("A man with one watch knows what time it is; " +
                  "a man with two watches is never quite sure. - Lee Segall -");
    };

    obs();
}

@functions{
    delegate void Observation();
}

Parameterized Delegates and Lambda Expressions

You can create an anonymous function or 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:

@page
@model Exercises.Pages.ExerciseModel
@using static System.Console
@{
    Observation obs = delegate(string x)
    {
        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);
}

@functions{
    delegate void Observation(string arg);
}

Instead of the delegate keyword, you can define an anonymous function or 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 function or method, use the argument or ignore it as you see fit. Here is an example:

@page
@model Exercises.Pages.ExerciseModel
@using static System.Console
@{
    Observation obs = (string x) =>
    {
        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);
}

@functions{
    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:

@page
@model Exercises.Pages.ExerciseModel
@using static System.Console
@{
    Observation obs = (x) =>
    {
        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);
}

@functions{
    delegate void Observation(string arg);
}

In the same way, you can to 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:

@page
@model Exercises.Pages.ExerciseModel
@using static System.Console
@{
    double operand1 = 92_735;
    string oper = "-";
    double operand2 = 39_488;

    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(number1.ToString() + " " + oper + " " + number2.ToString() + " = " + result.ToString());
    };

    exam(operand1, oper, operand2);
}

@functions{
    delegate void Operation(double x, string op, double y);
}

This would produce:

92735 - 39488 = 53247

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

@page
@model Exercises.Pages.ExerciseModel
@using static System.Console
@{
    double operand1 = 31_746;
    string oper = "/";
    double operand2 = 447;

    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;
	}

	WriteLine(number1.ToString() + " " + oper + " " + number2.ToString() + " = " + result.ToString());
    };

    exam(operand1, oper, operand2);
}

@functions{
    delegate void Operation(double x, string op, double y);
}

This would produce:

31746 / 447 = 71.02013422818791

Once again, the data types of the parameters are optional. Here is an example:

@page
@model Exercises.Pages.ExerciseModel
@using static System.Console
@{
    double operand1 = 29_723_592;
    string oper = "+";
    double operand2 = 6_858_494;

    Operation exam = (number1, oper, 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(number1.ToString() + " " + oper + " " + number2.ToString() + " = " + result.ToString());
    };

    exam(operand1, oper, operand2);
}

@functions{
    delegate void Operation(double x, string op, double y);
}

This would produce:

29723592 + 6858494 = 36582086

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:

@functions{
    delegate void Observation();

    public class Exercise
    {
    }
}

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:

@functions{
    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:

@functions{
    delegate void Observation();

    void Hold()
    {
    }

    void Create(Observation speech)
    {
    }
}

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

@functions{
    delegate void Observation();

    void Hold()
    {
    }

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

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:

@page
@model Exercises.Pages.ExerciseModel
@using static System.Console
@{
    Create(Hold);
}

@functions{
    delegate void Observation();

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

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

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:

@page
@model Exercises.Pages.ExerciseModel
@using static System.Console
@{
    Create(Hold);
}

@functions{
    delegate void Observation();

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

        speech();
    }    
}

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

@page
@model Exercises.Pages.ExerciseModel
@using static System.Console
@{
    Create(Hold);
            
    void Create(Observation speech)
    {
        speech();
    }
}

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

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:

@page
@model Exercises.Pages.ExerciseModel
@using static System.Console
@{
    Create(Hold);
}

@functions{
    delegate void Observation();

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

            speech();
        }
    }
}

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

@page
@model Exercises.Pages.ExerciseModel
@using static System.Console
@{
    Create(Hold);
}

@functions{
    delegate void Observation();

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

        speech();
    }    
}

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:

@page
@model Exercises.Pages.ExerciseModel
@using static System.Console
@{
    Create(Hold);

    void Hold() {}
}

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

        speech();
    }
}

Or like this:

@page
@model Exercises.Pages.ExerciseModel
@using static System.Console
@{
    Create(Hold);

    void Hold() {}
}

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

        speech();
    }
}

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:

@using static System.Console

@functions{
    delegate void Observation(string x);

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

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:

@page
@model Exercises.Pages.ExerciseModel
@using static System.Console
@{
    Create(Hold);
}

@functions{
    delegate void Observation(string x);
    
    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 -");
    }
}

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:

@page
@model Exercises.Pages.ExerciseModel
@using static System.Console
@{
    Sociology soc = new Sociology();
    Observation obs = new Observation(soc.Create);

    obs();
}

@functions{
    delegate void Observation();

    public class Sociology
    {
        public void Create()
        {
            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.


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