Author avatar

Dániel Szabó

Local Functions for C#

Dániel Szabó

  • Oct 25, 2019
  • 7 Min read
  • 12 Views
  • Oct 25, 2019
  • 7 Min read
  • 12 Views
Languages Frameworks and Tools
C#

Introduction

Local functions became part of C# with version 7.0. The idea is the definition of a function within another function, similar to python decorators. Another way to explain it is that a local function is a private function of a function, and its scope is limited to the function within which it was defined.

Local functions are similar to anonymous methods. Sometimes it's not necessary to create a named function to perform a specific action because functionality is only local to a specific function, and a named function would only pollute the outer scope.

We will learn more about local functions and examine several use cases in this guide.

Local Functions

The type of a local function bears resemblance to the type of the container function; however, there is no strict rule or enforcement from the compiler's side. When you have a local function you can use the async and unsafe modifiers.

You cannot define a local function in an expression-bodied member. The local function's purpose is to make your program more readable, and you are prevented from calling the local function by mistake as you cannot call it directly. You cannot use the static keyword in local functions, and you cannot apply attributes to a local function or to either one of its parameters or parameter type.

Common places where local functions are defined:

  1. Methods
  2. Constructors
  3. Property accessors
  4. Event accessor
  5. Lambda expressions
  6. Finalizers, a.k.a destructors
  7. Local functions (meaning local functions can be nested within each other)

A useful feature of local functions is that they allow exceptions to surface immediately. When you are using iterators, for example, exceptions are only surfaced when you enumerate them.

Simple Local Function

Let's demonstrate a simple local function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System;

namespace loca
{   
    class LocalFunctions
    {
        static void Main(string[] args)
            {
            void Message(string say) {
                Console.WriteLine($"The message says: {say}");
            }
            Message("Welcome");
            Message("to");
            Message("the");
            Message("Written");
            Message("Guides");
            Console.ReadKey();
            }
    }
}
csharp

Calling the code gives us the following output.

1
2
3
4
5
The message says: Welcome
The message says: to
The message says: the
The message says: Written
The message says: Guides
bash

This is the simplest form of a local function. The function Message takes a string and outputs a message to the console.

This code demonstrates how local functions can access variables from different scopes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using System;

namespace loca
{   
    class LocalFunctions
    {
        static void Main(string[] args)
            {
            int k = 10;
            double j = 99.9;
            void Scope(string x, int y) {
                Console.WriteLine($"The value of {nameof(x)} is {x}");
                Console.WriteLine($"The value of {nameof(y)} is {y}");
                Console.WriteLine($"The value of {nameof(k)} is {j}");
                Console.WriteLine($"The value of {nameof(j)} is {j}");
            }
            Scope("twelve", 666);
            Console.ReadKey();
            }
    }
}
csharp

The output should look like this.

1
2
3
4
The value of x is twelve
The value of y is 666
The value of k is 99.9
The value of j is 99.9
bash

The local variables called k and j are accessible for the function as they are defined inside the container function.

Generic Local Function

Let's create a generic local function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using System;

namespace loca
{   
    class LocalFunctions
    {
        static void Main(string[] args)
            {
            void MyGeneric<Value>(Value x) {
                Console.WriteLine($"Value from generic message: {x}");
            }
            MyGeneric<string>("twelve");
            MyGeneric<int>(2012);
            Console.ReadKey();
            }
    }
}
csharp

The output gives us:

1
2
Value from generic message: twelve
Value from generic message: 2012
bash

Generic functions combine reusability, type safety and efficiency. They are often used with collections and methods, which operate on them.

You can also reference out parameters in local functions that are declared in the same scope. Let's see how.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System;

namespace loca
{   
    class LocalFunctions
    {
        static void Main(string[] args)
            {
            void MyOut(string x, out string s) {
                s = $"Value from generic message: {x}";
            }
            string message = null;
            MyOut("twelve", out message);
            Console.WriteLine($"The value of out message: {message}");
            Console.ReadKey();
            }
    }
}
csharp

The output of the function call is as follows:

1
The value of out message: Value from generic message: twelve
bash

The out message variable captures the message and works in tandem with the out string s argument of the function.

Using **Params**

Finally we inspect the usage of params. This keyword allows the method or function to take variable number of arguments.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System;

namespace loca
{   
    class LocalFunctions
    {
        static void Main(string[] args)
            {
            void MyParams(params int[] array) {
                foreach (int element in array) {
                    Console.WriteLine($"Got the number of from the pipeline: {element}");
                }
            }
            int[] testArray = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

            MyParams(testArray);
            Console.ReadKey();
            }
    }
}
csharp

This produces the following output.

1
2
3
4
5
6
7
8
9
10
Got the number of from the pipeline: 1
Got the number of from the pipeline: 2
Got the number of from the pipeline: 3
Got the number of from the pipeline: 4
Got the number of from the pipeline: 5
Got the number of from the pipeline: 6
Got the number of from the pipeline: 7
Got the number of from the pipeline: 8
Got the number of from the pipeline: 9
Got the number of from the pipeline: 10
bash

Conclusion

In this guide we inspected use cases for local functions in C#. From the demo apps, we see how much functionality is brought to the language and how it opens up new possibilities for developers to restructure their applications in a more efficient way. I hope this has been informative to you and you have found what you were looking for. Give it a thumbs up if you liked it.

0