Skip to main content

C# Local Functions

Introduction

Local functions, introduced in C# 7.0, allow you to define functions inside other methods. Think of them as "functions within functions" that are only accessible within the containing method. They provide a way to encapsulate logic that's only relevant to a specific method, making your code cleaner and more maintainable.

In this tutorial, we'll explore how to create and use local functions, understand their benefits, and see practical examples of when they're most useful.

What Are Local Functions?

A local function is a function that is declared inside another member and is only accessible within that member. The containing member could be a method, constructor, property accessor, event accessor, or another local function.

Basic Syntax

Here's the basic syntax for defining a local function:

csharp
void OuterMethod()
{
// Local function definition
int AddNumbers(int a, int b)
{
return a + b;
}

// Calling the local function
int result = AddNumbers(5, 10);
Console.WriteLine($"Result: {result}");
}

When you run this code and call OuterMethod(), the output will be:

Result: 15

Benefits of Local Functions

Local functions offer several advantages over regular methods or lambda expressions:

  1. Encapsulation: They hide implementation details that are only relevant to a specific method
  2. Readability: They keep related code closer together
  3. Access to local variables: They can access variables from the containing method
  4. Performance: They can be more efficient than lambda expressions as they avoid closure allocations in certain scenarios
  5. Full language features: They support generics, ref/out parameters, and other C# features that lambdas may not

How Local Functions Work

Accessing Variables from the Enclosing Scope

Local functions can access variables defined in their containing method:

csharp
void ProcessOrder(Order order)
{
decimal discount = CalculateDiscount(order);

// Local function that uses the 'discount' variable from the outer scope
decimal ApplyDiscount(decimal price)
{
return price * (1 - discount);
}

// Using the local function
order.TotalPrice = ApplyDiscount(order.SubTotal);
Console.WriteLine($"Final price after {discount*100}% discount: {order.TotalPrice:C}");
}

Static Local Functions

Starting with C# 8.0, you can declare local functions as static. This prevents them from capturing variables from the enclosing scope, which can help avoid potential bugs and improve performance:

csharp
void CalculateValues(int[] values)
{
int threshold = 10;

// Static local function - cannot access 'threshold'
static double GetAverage(int[] numbers)
{
return numbers.Length > 0 ? numbers.Average() : 0;
}

double avg = GetAverage(values);
Console.WriteLine($"Average: {avg}");

// This would cause a compilation error:
// static double InvalidFunction() { return threshold * 2; }
}

Practical Examples of Local Functions

Example 1: Input Validation

Local functions are excellent for validating input parameters:

csharp
public int Divide(int numerator, int denominator)
{
// Local function to validate input
void ValidateDenominator(int d)
{
if (d == 0)
{
throw new ArgumentException("Denominator cannot be zero.", nameof(denominator));
}
}

// Call the validation function
ValidateDenominator(denominator);

// Perform the calculation
return numerator / denominator;
}

Example 2: Recursive Algorithms

Local functions work well for recursive algorithms where the recursive function doesn't need to be exposed:

csharp
public int Factorial(int n)
{
// Input validation for the public method
if (n < 0)
throw new ArgumentException("Input must be non-negative", nameof(n));

// Local recursive function
int CalculateFactorial(int number)
{
if (number <= 1)
return 1;

return number * CalculateFactorial(number - 1);
}

return CalculateFactorial(n);
}

For example, calling Factorial(5) would return:

120

Example 3: Iterator Methods

Local functions can improve iterator methods by separating the validation logic:

csharp
public IEnumerable<int> GetEvenNumbers(int max)
{
// Validate once before starting iteration
if (max < 0)
throw new ArgumentException("Maximum value must be non-negative", nameof(max));

// Local function that contains the iterator logic
IEnumerable<int> GetEvenNumbersCore()
{
for (int i = 0; i <= max; i += 2)
{
yield return i;
}
}

// Return the iterator
return GetEvenNumbersCore();
}

Usage example:

csharp
foreach (var num in GetEvenNumbers(10))
{
Console.Write($"{num} ");
}

Output:

0 2 4 6 8 10

When to Use Local Functions

Local functions are most useful when:

  1. The function is only relevant to a single method
  2. You want to hide implementation details
  3. You need to perform validation before starting an iterator
  4. You're implementing a recursive algorithm
  5. You want to break down a complex method into smaller parts without creating separate class methods

Local Functions vs Lambda Expressions

While local functions and lambda expressions seem similar, they have important differences:

FeatureLocal FunctionsLambda Expressions
NamedYesNo (anonymous)
HoistingDefined before or after useMust be defined before use
Parameter typesCan use ref/outLimited support
GenericsSupportedLimited support
Recursive callsStraightforwardMore complex
PerformanceMay be faster (no closure allocations)May require allocations

Summary

Local functions are a powerful feature in C# that allow you to define functions within other methods. They help improve code organization, readability, and performance by keeping related logic together and avoiding unnecessary allocations.

Key points to remember:

  • Local functions are defined within other methods and are only accessible there
  • They can access variables from the containing scope (unless declared as static)
  • They're ideal for validation, recursive algorithms, and iterator methods
  • They offer better performance and more features than lambda expressions for many use cases

Exercises

  1. Create a method that uses a local function to find the greatest common divisor (GCD) of two numbers using the Euclidean algorithm.
  2. Implement a binary search algorithm using a local function for the recursive part.
  3. Write a method that generates Fibonacci numbers up to a limit, using a local function for the calculation logic.
  4. Create a method that validates complex business rules using multiple local functions.

Additional Resources



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)