In this article, we will learn about the local functions in C#. We’ll explore the significance of local functions and how we can create them. Furthermore, we will discuss the best practices and compare them with lambda expressions.

To download the source code for this article, you can visit our GitHub repository.

Let’s start.

What Are Local Functions in C#?

Local functions are private functions that let us declare a method inside the body of a method that has previously been specified. Therefore, the usage of local functions improves clarity and readability. 

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

How to Create a Local Function

Let’s look at how we create a local function in C#: 

public string GetFullNameUsingFirstAndLastName(string firstName, string lastName)
{
    return GetFullName();

    string GetFullName()
    {
        return firstName + " " + lastName;
    }
}

Here, we use the GetFullNameUsingFirstAndLastName() method to create a complete name using the firstName and lastName inputs. By encapsulating this logic within this code structure, we create a more logical and manageable code.

Use Local Functions

Let’s consider an example of a product being checked out from a cart.

First, let’s create the Product class:

public class Product
{
    public string Name { get; init; }
    public int StockQuantity { get; init; }
    public int SelectedQuantity { get; init; }
}

Here, we create a Product class with three properties. Name is used for storing the name of the product. StockQuantity represents the property for tracking the quantity of the product in stock and SelectedQuantity keeps track of the quantity of the product selected.

Next, let’s create the ShoppingCart class:

public class ShoppingCart
{
    public List<Product> Products { get; init; }

    public bool Checkout()
    {
        foreach (var product in Products)
        {
            if (!IsItemInStock(product))
            {
                LogValidationFailure($"Product '{product.Name}' is out of stock.");
                return false;
            }

            if (!IsQuantityValid(product, product.SelectedQuantity))
            {
                LogValidationFailure($"Invalid quantity selected for '{product.Name}'.");
                return false;
            }
        }

        return true;

        bool IsItemInStock(Product product)
        {
            return product.StockQuantity > 0;
        }

        bool IsQuantityValid(Product product, int quantity)
        {
            return quantity > 0 && quantity <= product.StockQuantity;
        }

        void LogValidationFailure(string message)
        {
            Console.WriteLine($"Validation Error:- {message}");
        }
    }
}

First, we create a class ShoppingCart that has a product list and enables us to add more goods. Additionally, we create a Checkout() method that carries out some validation.

Then, we add local functions for IsItemInStock(), IsQuantityValid(), and LogValidationFailure() inside the checkout procedure. It checks the accuracy of the selected amounts and the product availability and only returns true if the validation is successful.

Further, let’s have a look at the checkout process with validation:

var cart = new ShoppingCart
{
    Products = new List<Product>
    {
        new() { Name = "Product A", StockQuantity = 10, SelectedQuantity = 2 },
        new() { Name = "Product B", StockQuantity = 11, SelectedQuantity = 5 }
    }
};

Console.WriteLine(cart.Checkout() ? "Checkout successful." : "Checkout failed due to validation errors.");

We create a ShoppingCart with a list of two products. Then, we check whether the cart is suitable for checkout or not using the cart.Checkout() method. Subsequently, we provide the user with feedback, on whether or not the checkout was successful.

Local Functions vs Lambda Functions

In C#, both local functions and lambda functions are valuable tools for defining functions, however, they serve different purposes and have distinct syntax and use cases.

Let’s illustrate the comparison between the local function and lambda function:

var numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var evenNumbers = FilterEvenNumbers(numbers);
Console.WriteLine(string.Join(", ", evenNumbers));

Here, we are trying to filter the list of even numbers from the list of integers by calling a method named FilterEvenNumbers().

Now, we build a function which encapsulates the logic for checking whether a number is even:

static List<int> FilterEvenNumbers(List<int> numbers)
{
    return numbers.Where(IsEven).ToList();

    bool IsEven(int num) => num % 2 == 0;
}

Here, we are trying to filter the list of even numbers from the list of integers by calling a method named FilterEvenNumbers().

Now, let’s rewrite the FilterEvenNumbers() method to use the lambda function instead of the local function:

static List<int> FilterEvenNumbers(List<int> numbers)
{
    return numbers.Where(n => n % 2 == 0).ToList();
}

Here, we use an inline lambda expression n => n % 2 == 0 directly within the Where() method. 

We can see that we can achieve the same result in different approaches. The usage of a local function or lambda function depends on certain tasks and developer preferences.

Best Practices for Local Functions

When a method’s code repeats, it is best to reorganize it into a local function to improve maintainability. Additionally, this allows code reuse.

We need to be aware that the local function can capture variables from the containing method’s scope. This happens due to the nature of how a local function is created on the low-level C#. When a local function captures an outer variable in the enclosing scope, it’s being generated as a delegate.

If the local function contains important business logic we should consider unit testing it. However, it’s not possible to test local functions directly. The only way of testing it is via testing the containing method. In such a case, we should consider avoiding using the local function and moving it to a separate method that can be tested.

The use of local functions for simple and minor logic might add unnecessary complexity to the code. Local functions can make debugging more complex, particularly if we have a large number of nested functions. So, we should keep an eye on the nesting level and keep each local function small and focused.

Ultimately, we should prioritize readability, maintainability, and performance when deciding whether to employ local functions, taking into account the particular context and requirements of our code.

Conclusion

We have discussed the local function in C# and its implementation. In addition to this, we have demonstrated the comparison of local functions against the lambdas function. Moreover, the use of best practices helps developers improve code reusability and maintainability. 

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!