Static Anonymous Functions with C# 9 - Quick overview

ยท

3 min read

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.

The reason

Declaring anonymous functions as static gives us:

  1. the ability to avoid unintentional capturing of state,
  2. more performant delegates due to less unexpected allocations,
  3. better functional programming with C#, in my opinion.

Example

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.

The fix

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.

The limitations

If you've not noticed already, there are a few limitations when using static anonymous functions:

  1. they cannot capture state from enclosing scope: locals, parameters, this.
  2. they cannot reference instance members from this or base.
  3. they can only reference static members and const definitions from the enclosing scope.
  4. nameof() can reference locals, or this or base from the enclosing scope (because it's evaluated at compile time).
  5. a non-static local 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.

ย