Index
1 - What is a delegate
We can say that a delegate is a reference to a method.
When we define a delegate, we are declaring a variable that points to another method.
We can define a delegate using the delegate
keyword followed by the function declaration as shown below:
<access modifier> delegate <return type> <name>(<parameters>[]);
Which can be translated to the following code:
public delegate void ImprimirDelegado(string value);
1.1 - Delegate example
Now it's time for a little example:
We have the following code:
namespace PA.Delegates
{
public delegate void ImprimirDelegado(string value);
public class EjemploDelegado
{
private void Imprimir(string valor)
{
Console.WriteLine(valor);
}
public void EjemploDelegate()
{
//Declaration
ImprimirDelegado imprimirDelegado = new ImprimirDelegado(Imprimir);
//invocation
imprimirDelegado("texto de ejemplo");
}
}
}
We should pay attention to several points:
- First, the delegate can be outside the class, although it can also be inside.
- Then there's a class containing:
- A method called "Imprimir" that has the same input type and return type as the delegate.
- A main method which declares the delegate and passes the "Imprimir" method we just created as a parameter.
- Invoking the delegate, passing text as parameter.
It is important to keep in mind that the return type and the parameters of the method must match between the method and the delegate. Otherwise, we will get a compilation error.
2 - Working with delegates and generics
The example we just saw is very simple and serves as an introduction, but before moving further, it must be noted that delegates can also work with generics.
If you are not clear about generics concepts, it is advisable to review this link about generics in C#.
First, we modify the value to accept generics.
public delegate void ImprimirDelegado<T>(T value);
And then we do the same with the method. In this case, we will create two methods.
The one we already have, and one that accepts an int
as parameter.
And when we declare it, we only need to indicate the type it will store:
public class EjemploDelegado
{
private void Imprimir(string valor)
{
Console.WriteLine(valor);
}
private void Imprimir(int valor)
{
Console.WriteLine($"el valor es {valor}");
}
public void EjemploDelegate()
{
//Declaration
ImprimirDelegado<string> imprimirDelegado = new ImprimirDelegado<string>(Imprimir);
ImprimirDelegado<int> imprimirDelegadoEntero = new ImprimirDelegado<int>(Imprimir);
//invocation
imprimirDelegado("texto de ejemplo");
imprimirDelegadoEntero(25);
}
}
Within .NET there are several delegate types already implemented that are used almost every day. These are Func
, Action
, and Predicate
.
3 - Action<T> Delegate
An Action
delegate points to a method that returns void
. The Action<T>
delegates always return void
, but they can have 0 to 16 parameters. The types of these parameters are determined by the generic types.
For example: an Action<string>
is a method that returns void
but requires a string
as input.
Which is exactly the Imprimir
method we've already implemented.
So, we can assign this method to Action<string>
and then invoke it.
private void Imprimir(string valor)
{
Console.WriteLine(valor);
}
public void EjemploAction()
{
Action<string> imprimirAction = Imprimir;
imprimirAction("ejemplo");
}
3.1 - Anonymous delegate methods
Very similar to what we know as anonymous types, we have anonymous methods.
When we define an Action
, we can directly assign the value of the delegate it references:
public void EjemploActionAnonimo()
{
Action<string> imprimirAction = delegate (string valor)
{
Console.WriteLine(valor);
};
imprimirAction("ejemplo");
}
This code can be improved with a lambda expression
which we will discuss later:
Action<string> imprimirAction = v => Console.WriteLine(v);
As mentioned, we will cover this in more detail later.
4 - Func<in T, out TResult> Delegate
Similar to the previous case, it accepts 1 to 16 parameters and their types are generic. The difference is that in this case, Func<in T, out TResult>
must return a value, and the last type passed to Func
is the return type.
In other words, a Func<int, string>
is a function that takes an int
and returns a string
:
Func<int, string> resultado = v => $"el resultado es{v}";
Console.WriteLine(resultado(5));
As noted, it accepts multiple parameters, for example, if we do a multiplication.
Func<int, int, int> multiplicacion = (v1, v2) => v1 * v2;
int valor = multiplicacion(3, 2);
Console.WriteLine($"El resultado es {valor}");
5 - Predicate<T> Delegate
Similar to the previous two, in this case, a Predicate
ALWAYS returns a boolean
. For example, Predicate<int>
takes an int
and returns a boolean
.
Predicate<int> mayorDeEdad = e => e >= 18;
bool esMayorDeEdad = mayorDeEdad(10);
Conclusion
In this post, we have seen how to instantiate a delegate and how to use the Func, Action, and Predicate
delegates. Having a clear understanding of delegates is extremely useful.
We will use delegates every day, which will help us write cleaner and higher-quality code.
But not only that, when we use LINQ
, we are constantly using Func<T>
.
If there is any problem you can add a comment bellow or contact me in the website's contact form