Static Anonymous Functions with C# 9 - Quick overview
C# version 9 introduced the ability to make anonymous functions
static. Read further for a quick explanation about why they were introduced, what problems they solve and when you should use them.
Declaring anonymous functions as
static gives us:
- the ability to avoid unintentional capturing of state,
- more performant delegates due to less unexpected allocations,
- better functional programming with C#, in my opinion.
int nonStaticVar = 1;
DoSomething(x => x + nonStaticVar);
In the above example, the anonymous function
x => x + nonStaticVar, depending on how it is used, can create a few allocations.
At least one allocation is needed for the delegate and optionally another allocation for the closure created over any captured state.
If a lambda (or anonymous function) is used in a loop or other hot path (like LINQ), C# can end doing a lot of memory allocations, which slows down performance because the garbage collector needs to do a lot of cleanup afterwards.
If you don't know what delegates are, they are just function pointers that aid with adding external custom logic to objects and calling (free) functions at runtime.
A delegate allows us to call any function whose signature matches the delegate. To work, a delegate needs to be instantiated with a reference to a particular function. C# usually does this automatically, because of syntactic sugar and implicit conversions in newer versions of the language, but in the first versions of the language, delegates had to be instantiated manually.
const int constVar = 1; // could be static also
DoSomething(static x => x + constVar);
Declaring the above lambda static makes sure that the delegate is only instantiated once, and only when it's first used. Also, no closure it needed because we're not capturing instance variables, just a constant.
The lambda is now a pure function, in functional programming terms. But you can still modify static variables, so be careful, because that wouldn't be a pure function anymore, i.e. using
static is not a syntactic guarantee that the function cannot be made un-pure.
If you've not noticed already, there are a few limitations when using static anonymous functions:
- they cannot capture state from enclosing scope: locals, parameters,
- they cannot reference instance members from
- they can only reference
constdefinitions from the enclosing scope.
nameof()can reference locals, or
basefrom the enclosing scope (because it's evaluated at compile time).
non-staticlocal function or anonymous function can capture state from an enclosing
static anonymous function, but not outside its bounds.
Key take away
When possible, to avoid unnecessary and wasteful memory allocation, consider using static anonymous functions and designing your functions so that they take every input they need as parameters.
If you need to capture non-static state though, consider first if you can make that state static or const.