Local Functions – What’s the Value?

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.

Music Note - C# 7

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:


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:


string[] names = { "James", "Niki", "John", "Gerhard", "Jack" };
var q = names.Where(null);
foreach (var n in q) // callstack position for exception
{
Console.WriteLine(n);
}

view raw

Program.cs

hosted with ❤ by GitHub

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.


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.


string[] names = { "James", "Niki", "John", "Gerhard", "Jack" };
var q = names.Where(null);
foreach (var n in q) // callstack position for exception
{
Console.WriteLine(n);
}

view raw

Program.cs

hosted with ❤ by GitHub

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:


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.


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);
}

view raw

Quicksort.cs

hosted with ❤ by GitHub

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#?


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:


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:


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.

More information about C# 7 in my Workshops and Books

8 thoughts on “Local Functions – What’s the Value?

  1. Please see my post about C# 7 features (https://codingforsmarties.wordpress.com/2017/04/05/c-7-features-i-dont-like) and its follow-up specifically regarding local functions (https://codingforsmarties.wordpress.com/2017/04/27/local-functions-at-it-again). Your yield example is interesting, but it’s a lot of work just to throw an exception at declaration instead of invocation, especially since this is contrary to how the base Linq methods work.

    Like

    1. The base LINQ methods work in the same way. You probably had errors passing null because of different overloads. Just try this out:
      string[] data = { “one”, “two” };
      Func predicate = null;
      var q = data.Where(predicate);
      foreach (var item in q)
      {
      Console.WriteLine(item);
      }

      The exception happens with the initialization of q, not in the foreach.

      Cheers,
      Christian

      Like

      1. I’m quite familiar with how .Net works. My point in my post is that I don’t see much use in this feature. Examples that I’ve seen can be equally implemented using private (not local) methods or lambdas.

        Also, your example changes the expected behavior of a standard Linq function by throwing at a different point in execution. While I agree with your reason for doing this, the deviation from a common standard is enough for me to question the design. Developers that come after me and encounter this code may not expect it to behave the way it does, which could make it more difficult to debug.

        Liked by 1 person

  2. Of course, the local function can also be implemented as a private method or using lambdas. Sometimes it’s just a matter of taste.
    However, lambdas have additional overhead compared to local functions, and private methods can be called from other methods of the class as well. The scope is more restricted with local functions, and they can also access variables in the scope of the outer method (similar to lambdas but different to other private methods).

    I see your point in naming my method “Where”. Would you be fine if I would have named this method “Filter”? I just named it “Where” to demonstrate a simplified implementation of the Where method from the framework. However, the framework itself has multiple Where methods implemented (not just overloads, but implementations in different classes).

    Like

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.