A great feature of C# 7.0 are local functions. Local functions have the syntax of methods and can be used within the scope of methods, properties, constructors…
With articles showing this feature, I often see questions: “What is it good for?” “Why is this needed?” To understand the usefulness of local functions, some good examples are needed. I try to show these with this article.

Local Functions with the yield Statement
Let’s start with a simplified filter method Where. This implementation checks for
parameters resulting in an ArgumentNullException in case null is passed:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, bool> predicate) | |
| { | |
| if (source == null) throw new ArgumentNullException(nameof(source)); | |
| if (predicate == null) throw new ArgumentNullException(nameof(predicate)); | |
| foreach (T item in source) | |
| { | |
| if (predicate(item)) | |
| { | |
| yield return item; | |
| } | |
| } | |
| } |
Invoking this method, the ArgumentNullException is not thrown when the query statement is defined, but – because of the delayed execution of yield, with the foreach iteration in line 4:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| string[] names = { "James", "Niki", "John", "Gerhard", "Jack" }; | |
| var q = names.Where(null); | |
| foreach (var n in q) // callstack position for exception | |
| { | |
| Console.WriteLine(n); | |
| } |
For having error information when it is needed, the Where method can be splitted into two methods. The Where method just checks the parameters without any yield statement included in the implementation, and invokes the WhereImpl method where the yield is done.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, bool> predicate) | |
| { | |
| if (source == null) throw new ArgumentNullException(nameof(source)); | |
| if (predicate == null) throw new ArgumentNullException(nameof(predicate)); | |
| return WhereImpl(source, predicate); | |
| } | |
| private static IEnumerable<T> WhereImpl<T>(IEnumerable<T> source, Func<T, bool> predicate) | |
| { | |
| foreach (T item in source) | |
| { | |
| if (predicate(item)) | |
| { | |
| yield return item; | |
| } | |
| } | |
| } |
With this in place, the ArgumentNullException happens in line 2 where the error is more helpful.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| string[] names = { "James", "Niki", "John", "Gerhard", "Jack" }; | |
| var q = names.Where(null); | |
| foreach (var n in q) // callstack position for exception | |
| { | |
| Console.WriteLine(n); | |
| } |
Now, the Where method has nothing than a parameter check and an invocation of the real implementation. Here, local functions are of great help. The implementation is simpler compared to the private method – the local function Iterator can access variables from the outer scope, and thus here parameters are not needed:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, bool> predicate) | |
| { | |
| if (source == null) throw new ArgumentNullException(nameof(source)); | |
| if (predicate == null) throw new ArgumentNullException(nameof(predicate)); | |
| return Iterator(); | |
| IEnumerable<T> Iterator() | |
| { | |
| foreach (T item in source) | |
| { | |
| if (predicate(item)) | |
| { | |
| yield return item; | |
| } | |
| } | |
| } | |
| } |
Recursive Functions
Another scenario for local functions are recursive calls.
In the following implementation of the QuickSort method, Sort is a local function that is called recursively until the collection is sorted.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| public static void QuickSort<T>(T[] elements) where T : IComparable<T> | |
| { | |
| void Sort(int start, int end) | |
| { | |
| int i = start, j = end; | |
| var pivot = elements[(start + end) / 2]; | |
| while (i <= j) | |
| { | |
| while (elements[i].CompareTo(pivot) < 0) i++; | |
| while (elements[j].CompareTo(pivot) > 0) j–; | |
| if (i <= j) | |
| { | |
| T tmp = elements[i]; | |
| elements[i] = elements[j]; | |
| elements[j] = tmp; | |
| i++; | |
| j–; | |
| } | |
| } | |
| if (start < j) Sort(start, j); | |
| if (i < end) Sort(i, end); | |
| } | |
| Sort(0, elements.Length – 1); | |
| } |
Using recursive calls with C# you need to be careful:
With C# you need to be careful with recursive calls. Contrary to functional programming languages like F#, the C# compiler does not tail call optimization where recursive method calls are converted to iterations to not consume call stack. With C# you can easily result in a
StackOverflowException.
When does it end with the default stack configuration doing recursive calls with C#?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| public static void WhenDoesItEnd() | |
| { | |
| Console.WriteLine(nameof(WhenDoesItEnd)); | |
| void InnerLoop(int ix) | |
| { | |
| Console.WriteLine(ix++); | |
| InnerLoop(ix); | |
| } | |
| InnerLoop(1); | |
| } |
This simple sample that doesn’t need a lot of stack memory with every iteration ends after 24020 iterations with a StackOverflowException, so be careful doing recursive calls with C#.
Local Functions instead of Lambda Expressions
Recently I came across an older sample where a Lambda expression is used in the AsynchronousPattern method:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| static void AsynchronousPattern() | |
| { | |
| WebRequest request = WebRequest.Create(url); | |
| IAsyncResult result = request.BeginGetResponse(ar => | |
| { | |
| using (WebResponse response = request.EndGetResponse(ar)) | |
| { | |
| Stream stream = response.GetResponseStream(); | |
| var reader = new StreamReader(stream); | |
| string content = reader.ReadToEnd(); | |
| Console.WriteLine(content.Substring(0, 100)); | |
| Console.WriteLine(); | |
| } | |
| }, null); | |
| } |
This one can be replaced by a local function as well:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| private static void AsynchronousPattern() | |
| { | |
| WebRequest request = WebRequest.Create(url); | |
| IAsyncResult result = request.BeginGetResponse(ReadResponse, null); | |
| void ReadResponse(IAsyncResult ar) | |
| { | |
| using (WebResponse response = request.EndGetResponse(ar)) | |
| { | |
| Stream stream = response.GetResponseStream(); | |
| StreamReader reader = new StreamReader(stream); | |
| string content = reader.ReadToEnd(); | |
| Console.WriteLine(content.Substring(0, 100)); | |
| Console.WriteLine(); | |
| } | |
| } | |
| } |
This is just a matter of taste, but doesn’t look the syntax with the local function easier?
What are your thoughts on local functions with C# 7?
Have fun programming and learning,
Christian
Some more C# 7 articles:
C# 7 – What’s New
C# 7.0 Pattern Matching
C# 7.0 Out Vars and Ref Returns
C# 7.0 Expression Bodied Members
Tuples with C# 7.0
Binary Literals and Digit Separators
More information on C# 7 features in my new book Professional C# 7 and .NET Core 2.0 with source code updates at GitHub.






Leave a comment