Professional Documents
Culture Documents
Slot 6 - Delegate
Slot 6 - Delegate
Hide
1. What is Delegate? Why is a delegate needed in C#?
1.1. Role of delegate in C#
1.2. Illustration
2. Programming techniques with delegates in C#
2.1. Declaring delegate type
2.2. Description of methods and delegates
2.3. Declare and use delegate variable
2.4. Pass a delegate type parameter to the method
3. Generic delegate
3.1. Actions
3.2. Funcs
3.3. Predicate
4. Using delegates with anonymous methods, lambda functions, local functions
4.1. Anonymous method
4.2. lambda . function
4.3. Local function
5. Conclude
Suppose we need to build a class, in this class will have to call a method to
perform a certain action. However, we do not know this method when building
the class! This method only appears when someone else uses the class to
instantiate the object.
Have you ever touched windows forms or wpf ? If you are sure you will notice,
the button (Button) is a built-in class, and the button event handler method
(OnClick) is written by you. How does the constructor of the Button class know
and run the event handler method you wrote?
Situations where it is not known in advance which specific method to call leads
to the use of a special kind of tool in C#: delegate.
1
The term "delegate" translated into Vietnamese can be either a representative type
or a proxy type . However, these two translations are not commonly used in the
literature. So, in this article we will use the original English term – delegate .
Each delegate type, when defined, only allows its variable to contain references
to methods that conform to this delegate's rules.
Delegates are used to help a class object interact back with the entity that
creates and uses that class. This keeps the class from being "isolated" within
that entity. For example, delegates help to call the methods of the actual
creation and containment of the class object.
With the ability to create such backward interactions, delegates become the
core of the event-driven programming model, used in winforms and WPF
technologies.
For example, when building windows form controls (buttons, select buttons,
menus, etc.), the programmer of these classes cannot define what the user
wants to do when the button is clicked, when the menu is selected. . Therefore,
it is imperative to use the mechanism of delegates to pass this logic on to users
of those classes to write code. Thus, the Button when created in a Form has the
ability to interact with the Form's code, not isolate itself.
Delegates are also commonly used with the asynchronous programming model
in the form of callback methods, or in multithreaded programming.
2
calculates and prints the value of a function in a range of values. The actual
function will be created by the user of the class and provided later.
Note:
Class: Graph
using System;
namespace ConsoleApp
{
/* declare the MathFunction delegate type:
* this type is described as (double) -> double
* means that any function can be assigned "taking a variable of type double,
* returns type double" for a variable of type MathFunction
*/
internal delegate double MathFunction ( double x ) ;
// simulate the graphing of functions
internal class Graph
{
/* declares a property of type MathFunction.
* MathFunction is used as normal data types
*/
public MathFunction Function { get ; set ; }
/* This method takes an input parameter of type delegate MathFunction.
* The delegate type as a parameter is no different from the normal data type
*/
public void Render ( MathFunction function, double [] range )
{
// can assign variables of type delegate as usual
Function = function;
// since function is a normal object, it also has properties
// and methods like other objects. Practically all delegate types are
// inherit the System.Delegate class. Here we are using the Method . attribute
// of this class.
Console. WriteLine ( $ "Drawing the function graph: {function.Method} " ) ;
foreach ( var x in range )
{
// even though a function is an object, it can be "called" like a function call.
3
// this is the difference between object of type delegate and object
//create from normal class
var y = function ( x ) ;
// In addition to this call, you can also use the following structure
// var y = function.Invoke(x);
//
// var y = function?.Invoke(x);
Console. Write ( $ " {y:f3} " ) ;
}
Console. WriteLine ( "rn-----------------" ) ;
}
}
// a test class containing methods with description (double)->double
internal class Mathematics
{
// here is an instance method
public double Cos ( double x ) = > Math. Cos ( x ) ;
// this is a static method
public static double Tan ( double x ) = > Math. Tan ( x ) ;
}
internal class Program
{
// another static method
private static double Sin ( double x )
{
return Math. Sin ( x ) ;
}
private static void Main ( string [] args )
{
Graph graph = new Graph () ;
// initialize the value range of x
double [] range = new double [] { 1.0 , 1.2 , 1.3 , 1.4 , 1.5 , 1.6 , 1.7 , 1.8 , 1.9 , 2.0 } ;
// pass Sin function as parameter to Render
graph. Render ( Sin, range ) ;
// pass static Tan function to Render
graph. Render ( Mathematics Tan , range ) ;
// pass the Cos instance function to Render
Mathematics math = new Mathematics () ;
graph. Render ( math. Cos , range ) ;
// pass a built-in function Sqrt of the Math class in .net
graph. Render ( Math. Sqrt , range ) ;
// create an anonymous function that conforms to the description (double) -> double
4
// and assign it to the function variable
// variable function is a variable of type delegate MathFunction
MathFunction function = delegate ( double x ) { return x *= 2 ; } ;
// pass function variable to Render function
graph. Render ( function, range ) ;
// declare and pass anonymous function directly at parameter position
graph. Render ( delegator ( double x ) { return x++; } , range ) ;
// declare and pass lambda function directly at parameter position
graph. Render (( double x ) = > { return x *= 10 ; } , range ) ;
// pass a shortened lambda function as parameter
graph. Render ( x = > x / 10 , range ) ;
Console. ReadKey () ;
}
}
}
syntax
5
Description of cv and delegate methods _ _ _
To make it easier to describe the method (and delegate type), a descriptive
convention is given as follows:
When reading this description we will understand: the method (regardless of the
name) takes two input parameters of the same type int and returns a result of
type float.
6
A variable of type delegate is also an object, similar to objects created from a
class, it also has properties and methods like normal objects. In fact, all user-
defined delegate types inherit from the System.Delegate class , so they also
inherit the properties and methods of this class.
Because the delegate variable will contain a reference to a method, we can use
the delegate variable name like a real method, meaning we can "call" this
variable like calling a normal method. When "calling" a delegate variable, the
method it points to will be executed.
In addition to "calling" the delegate variable like calling a method, the delegate
types also have an Invoke method that calls the method this variable points to:
var y = function.Invoke(x);
More carefully we can check the object before calling Invoke:
var y = function?.Invoke(x);
Maths? Allows you to check if a function object has a null value. If the object has a non-null
value, the Invoke method is executed. This usage is the safest. If the function receives null
value, the Invoke method will not be called. Without checking for null, a method call on a
null object will raise an error.
7
// pass Sin function as parameter to Render
graph. Render ( Sin, range ) ; // public void Render(MathFunction function, double[] range)
Generic delegate
As we know when looking at delegates, to work with delegates we need to first
declare the delegate as a normal data type declaration, and then use that
delegate type to declare variables. At the use stage we assign that delegate
variable with a method that matches the requirements of the delegate type.
Actions
Actions are delegate types corresponding to methods that return no data
(output is void). Action types are defined in the System namespace as follows:
namespace System
{
public delegate void Action () ;
8
public delegate void Action < in T >( T obj ) ;
public delegate void Action < in T1, in T2 >( T1 arg1, T2 arg2 ) ;
// there are other similar delegates
// .NET framework defines a total of 16 such delegates with the number of input parameters from 1 to 16.
}
The .NET framework defines a total of 16 generic delegates, the first one has 1
input parameter, the last one has 16 input parameters.
Thus, the type Action<T1,..> can correspond to any method that returns no
value and has between 1 and 16 input parameters (any type). Only delegate
void Action() corresponds to methods that take no parameters and return no
value.
Funcs
Funcs are delegate types that correspond to methods that return data. The
funcs types are defined in the System namespace as follows:
namespace System
{
public delegate TResult Func < out TResult >() ;
public delegate TResult Func < in T, out TResult >( T arg ) ;
public delegate TResult Func < in T1, in T2, out TResult >( T1 arg1, T2 arg2 ) ;
// there are 17 similar delegates
}
Similar to actions, the .NET framework also defines 17 delegate types as above
with the number of input parameters from 0 to 16.
Funcs type definitions differ from actions in that funcs must always have an
output type at the end of the list of pseudo-generic types (out TResult). Here
are some examples using funcs:
9
Func < int > func1 = () = > 0 ;
Func < int , int > func2 = ( i ) = > i * 10 ;
Func < int , int , float > func3 = ( a, b ) = > a / b;
Console. WriteLine ( $ "func1: {func1} ; func2: {func2(10)} ; func3: {func3(1, 2)} " ) ;
The variable func1 is declared and refers to a lambda function that takes no
parameters and always returns 0 (of type int); variable func2 refers to a lambda
function that takes an int parameter, multiplies that parameter by 10 and
returns this result (of type int); variable func3 refers to a lambda function that
takes two integers and returns the division result (type float).
Predicate
Predicate is a predefined delegate type as follows (in System):
namespace System
{
public delegate bool Predicate <[ NullableAttribute ( 2 )] print T >( T obj ) ;
}
As we see in the examples above, we all use lambda functions with delegates.
When using this style we can completely omit the type declaration of the
parameter in the lambda function because C# can infer the type by itself. In
addition, if the method body has only one statement, use the "expression body"
writing style for brevity.
10
Due to the specificity of C#, methods must be defined in a class (whether
instance method or static method). For instance methods, we also have to
initialize the object before passing this method as a parameter to another
method via delegate.
If the method is only used once as a parameter, or the method is too simple
(consisting of only one line of code), building such small batches of methods can
cause code fragmentation. making it difficult to track and manage.
Anonymous cv method _ _ _ _
Anonymous methods are also a type of method, but differ from normal methods
in a number of ways:
We continue to use the illustrative example we did in the previous post. Here
we do not declare more normal methods as before, but directly declare an
anonymous method and assign it to a function variable of type MathFunction :
// create an anonymous function that conforms to the description (double) -> double
// and assign it to the function variable
// variable function is a variable of type delegate MathFunction
MathFunction function = delegate ( double x ) { return x *= 2 ; } ;
// pass function variable to Render function
graph. Render ( function, range ) ;
// declare and pass anonymous function directly at parameter position
11
graph. Render ( delegator ( double x ) { return x++; } , range ) ;
lambda . function
Lambda functions also have the characteristics of anonymous methods, but can
omit the type declaration of the parameters.
This comes from the fact that when a lambda function is declared as a
parameter of a method, its parameters must conform to the delegate's
description. Therefore, the C# compiler can infer the type of the parameters on
its own without having to write it out explicitly.
In the second scenario we see that the parameter list of the lambda function
doesn't even have a data type name.
The reason is because this function is used as the first parameter to the Render
method. This parameter has been specified as (double) -> double. Therefore,
the parameter x will be interpreted by C# as being of type double. This type
inference, combined with the expression body, makes lambda functions very
succinct.
12
Program execution results
Where we pass anonymous methods and lambda functions, the C# compiler
generates names for these functions on its own. However, since the code
doesn't have a name, we don't have the ability to call these functions again in
other places in the code.
cb tool function _
From C# 7, we have one more possibility to construct a method within another
method body besides using anonymous methods and lambda functions: using
local functions .
Local functions are not at all different from regular methods. However, the local
function does not have the access control keyword and can only be called within
the method body that contains it.
If you don't know: in C# there are two things that are different from many other languages
of the C/C++ family: (1) can declare classes inside classes, and (2) can declare methods
inside methods.
13