C# offers a lot of utility, especially around the delegate topic. So let's see what exactly a delegate is and how the distinct types like delegate
, Action
, Func
, Predicate
, anonymous function, lambda expressions and MulticastDelegate
behave. Before we dive into what the exact objects do, we have to know what they are used for.
It is very nice that we can pass objects around like int
or string
but sometimes we want to pass a method to a method. And that is all the magic in a very short. If you are familiar with C/C++ they are very much comparable with function pointers.
If you used LINQ, you are using delegates all the time. Look at Where
function. If you want to have all even numbers of a list you would do something like this. numbers.Where(i = > i % 2 == 0)
. The Where
function itself does not know how to filter and lets you decide how to apply a filter. It is only interested in two things: a bool
return value to know which elements to filter out and an object for you to build your filter logic.
delegate
The most important fact at the beginning and it is super important that you keep this in mind:
All of the above-listed types and things are delegates! All of them, no exception! This is vital to understand. They only differ in usage and when they were introduced into the .NET world.
To understand a delegate we can have a look at what a "natural" delegate is:
A person designated to act for or represent another or others; deputy; representative, as in a political convention.
And the technical version is similar. The delegate
represents someone to the outside world and the outside world only sees the delegate instead of the represented function. Another way of thinking about what a delegate is, is very much like abstract classes and abstract methods work. They give the frame but you are responsible (when you inherit) to fill that frame.
Let's have a look at the following example:
delegate void PrintText(string s);
We can see it has no body. It is just the pure definition which we then later can use.
DoMath addTwo = new DoMath(AddTwo);
DoMath multiplyByTwo = new DoMath(MultiplyByTwo);
Console.Write(addTwo(10));
Console.Write(multiplyByTwo(10));
int AddTwo(int number) => number + 2;
int MultiplyByTwo(int number) => number * 2;
delegate int DoMath(int number);
We have a delegate
and two functions that fulfill the structure of that very delegate
, and we can see as long as the signature matches, we can assign any of the functions to the delegate. Everyone who interacts with the delegate would not know which concrete function is behind it.
When would I use a delegate
?
- You are using a very old .NET framework version (like 1.1) - I guess very unlikely
- You have a complicated signature, which would be very hard to understand with the other mechanism like:
Func<IEnumerable<IEnumerable<MyObject>>>, int>
. A named delegate might be better here:delegate int ProcessBatches(IEnumberable<IEnumerable<MyObject>> chunks)
Multicast delegate
One special thing in .NET is that every delegate (also Action
, Func
and friends) is a multicast delegate. That means you can not only attach one function to a delegate but n functions (also 0 if you want). So basically, you can chain multiple commands to a single one. See it in action:
var printDelegate = new PrintToConsole(PrintLowerCaps);
printDelegate += PrintUpperCase;
printDelegate("Hello World");
void PrintUpperCase(string text) => Console.WriteLine(text.ToUpper());
void PrintLowerCaps(string text) => Console.WriteLine(text.ToLower());
delegate void PrintToConsole(string text);
The output on the console:
hello world
HELLO WORLD
Now one special thing is if your delegate returns a value, what will happen here? It's simple: The last chained method returns its value.
var printDelegate = new ReturnANumber(ReturnFive);
printDelegate += ReturnFour;
Console.Write(printDelegate());
int ReturnFive() => 5;
int ReturnFour() => 4;
delegate int ReturnANumber();
This will simply print 4 even though both functions got executed. Important thing is, that this applies to all delegates even though you might not use it that often.
Action
Action
s were added with the .NET Framework 2.0, and as said in the beginning, there is nothing more than a delegate. In this case a special delegate. An Action
is defined that it has no return value. There are multiple overloads that correspond to the number of parameters an action can have.
Action<string> printToConsole = text => Console.WriteLine(text);
Why do we have functions? What do they solve?
The first thing is: It is convenient. Look at the following example:
// Use Action
public void Print(Action<string> printAction) => printAction("Hello World");
// Use delegate
public delegate void PrintAction(string text);
public void Print(PrintAction printAction) => printAction("Hello World");
There is so much overhead when using delegates directly. You always have to define it somewhere instead of just the signature you expect.
The second thing is that delegate
s have some weird quirks and shortcomings. Look at the following example:
// Delegates: same signature but different types
public delegate void Foo();
public delegate void Bar();
// Consumer function -- note it accepts a Foo
public void Consumer(Foo f) {}
Consumer(new Foo(delegate() {})); // works fine
Consumer(new Bar(delegate() {})); // error: cannot convert "Bar" to "Foo"
There is no reason why this should not work. The structure is the same and from a technical point of view that should work, but it does not! But doing the same with an Action works without problem.
Action<string> printToConsole = PrintToConsole;
Action<string> anotherAction = PrintToConsole;
Console.WriteLine(printToConsole == anotherAction);
void PrintToConsole(string s) { }
Just returns true
. Super easy.
One thing one has to be aware is that Action
does not go well with async
and await
. The reason is simple, an Action can't return anything so it behaves like a void
function. Technically you would do async void
. So no chance to await the task:
Action doWait = async () => await Task.Delay(2000);
// async void - fire and forget
// We will not wait 2 seconds we just go right through
doWait();
// Compiler error: Type 'void' is not awaitable
await doWait();
Func
Func
, introduced with .NET Framework 3.5, is nothing special. It is also a delegate and behaves much like an Action
. The only difference is that a Func
has to return a value. And the return type is always the last defined generic parameter:
Func<int, int> MultiplyByTwo = n => n * 2;
Func<string, string, int> CountLengthOfBothStrings = (s, s1) => s.Length + s1.Length;
int number = MultiplyByTwo(10);
int length = CountLengthOfBothStrings("Hello", "World");
In contrast to Action
Func
does work well with async
and await
as you can return the Task
object. Taken our example from above we can easily make it awaitable:
Func<Task> doWait = async () => await Task.Delay(2000);
await doWait();
Predicate
Predicate
is just a special delegate
. In its core it is defined as this:
delegate bool Predicate<in T>(T obj);
Functions like List<T>.Exists
use this special delegate. The method signature could be easily the same as every LINQ query: Exists(Func<T, bool> predicate)
but for backwards compatibility they did not change it. Remember the delegate
keyword is in the Framework since version 1.1, Func
since version 3.5. List<T>
was introduced with version 2.0 so there was nothing else around at the time.
Anonymous function
Available since C# 2.0 the anonymous function is an inline delegate. We saw earlier in the delegate section that we always defined a "real" method to which the delegate was linked. But we can also do something like this:
DoMath addTwo = delegate(int number)
{
return number + 2;
};
Console.WriteLine(addTwo(2));
delegate int DoMath(int number);
This would of course print 4. What should be clear here as well is that this is still a regular delegate. So all the stuff which applied to delegates applies to anonymous functions as well.
Lambda expression
Lambda expressions, introduced with C# 7, are just a very convenient way of writing inline delegates. They are easily identifiable with the arrow notation:
Func<int, int> addTwo = n => n + 2;
Action sayHello = () => Console.WriteLine("Hello");
Console.WriteLine(addTwo(2));
Under the hood they are the same as anonymous functions. You can check all this stuff on sharplab.io.
Closure
Closures means that you bind a variable from outside scope of the function to the inside scope. Let's have a small look:
var someNumber = 100;
// We are binding "someNumber" inside our lambda expression
Func<int, int> addSomeNumber = n => n + someNumber;
Console.WriteLine(addSomeNumber(2));
So everytime you use an object inside your lambda expression/anonymous function/delegate, which is declared outside, the compiler has to close over it. It does that via some "magic". Behind the scenes it will generate a whole new class which captures your object. Why is this important? Well for multitude of reasons.
First:
var someNumber = 100;
Func<int, int> addSomeNumber = n => n + someNumber;
someNumber = 101;
Console.WriteLine(addSomeNumber(2));
The result is 103. If we would save the variable in the time where the lambda was defined we would print out 102, but we keep somehow the reference to that object.
Second. Imagine you define a function where your closed variable is not available anymore. This still has to work:
// DoMath() returns us the Func, which we have to invoke again
// Therefore we have ()()
// Alternatively you can use DoMath().Invoke()
Console.WriteLine(DoMath()());
Func<int> DoMath()
{
int someNubmer = 12;
return new Func<int>(() => someNubmer * 2);
}
How does C# do that?
Taken the example from above:
var someNumber = 100;
Func<int, int> addSomeNumber = n => n + someNumber;
someNumber = 101;
Console.WriteLine(addSomeNumber(2));
It will create this:
internal static class <Program>$
{
// This is our anonymous function
private sealed class <>c__DisplayClass0_0
{
// The integer gets captured
// Remember: Value Types on reference types get boxed so that we keep the reference
// Therefore if the "real" someNumbes changes, this object is changed as well
public int someNumber;
internal int <<Main>$>b__0(int n)
{
return n + someNumber;
}
}
private static void <Main>$(string[] args)
{
<>c__DisplayClass0_0 <>c__DisplayClass0_ = new <>c__DisplayClass0_0();
<>c__DisplayClass0_.someNumber = 100;
Func<int, int> func = new Func<int, int>(<>c__DisplayClass0_.<<Main>$>b__0);
// The compiler uses our class field instead of the variable
<>c__DisplayClass0_.someNumber = 101;
Console.WriteLine(func(2));
}
}
More in detail can be seen on sharplab.io.
Two things which are very important to understand when using closures:
- Value types do get boxed and put on the managed heap. I will put a link into the resources for me information about that.
- If you referencing big objects (or
this
) you will keep references around which could mean that the Garbage Collector is not able to remove your object. Just because it is living in a closure. Also for that, a link at the end.
Conclusion
I hope I could give you a good overview of what a delegate is and how it behaves in its different forms and shapes in .NET.
Resources
- Heap, Stack, Boxing and Unboxing, Performance ... let's order things!
- The garbage collector Part 1 and Part 2