Local Functions
Methods that are only accessible from within the containing method in which they are defined.
They can be declared and called from methods (especially iterator and async methods), constructors, property accessors, event accessors, anonymous methods, lambda expressions, finalizers, and other local functions.
Modifiers
Local functions support the following modifiers:
async
unsafe
static
- A static local function cannot capture local variables or instance state.
extern
- An external local function must be static.
All local functions are intrinsically private.
Variables in Local Functions
All local variables that are defined in the containing member, including its method parameters, are accessible to a local function (so long as it is not a static local function).
Attributes in Local Functions
Attributes can be applied to a local function, its parameters, and type parameters:
private static void Process(string?[] lines, string mark)
{
// …
bool IsValid([NotNullWhen(true)] string? line)
{
// …
}
}
Exceptions in Local Functions
Local functions can allow exceptions to surface immediately. This makes them especially useful in:
- Method iterators, where exceptions are only surfaced when the returned sequence is enumerated, not when the iterator is retrieved (when the IEnumerable is instantiated).
- Async methods, where exceptions thrown are not observed until the returned task is awaited.
Local Functions vs. Lambda Expressions
Local functions and lambda expressions are similar. Key differences:
Naming
Local functions are explicitly named, like methods.
Lambda expressions are anonymous methods and need to be assigned to variables of a delegate type, like Action or Func types.
- Lambda expressions rely on the return type of the Action/Func variable to which they are assigned to determine their argument and return types.
Definition
Local functions: defined at compile time (this is why they can be declared above or below return statements)
Lambda expressions: declared and assigned at run time.
Recursive Algorithms
Recursive algorithms are easier to create using local functions.
Delegates
Lambda expressions are converted to delegates when they’re declared.
Local functions can be written either as methods or delegates. They are only converted to delegates when they are used as a delegate.
Heap Allocations
Local functions can avoid heap allocations that are always necessary for lambda expressions. If a local function is never converted to a delegate, the compiler avoids a heap allocation.
Iterators
Local functions can be implemented as iterators using yield return to produce a sequence of values.
Example
public static int Factorial(int number)
{
if (number < 0)
{
throw new ArgumentException(…);
}
return localFactorial(number);
int localFactorial(int localNumber)
{ // Local function.
if (localNumber < 1) { return 1; }
return localNumber * localFactorial(localNumber - 1);
}
}