Local or nested functions in C# 7

With C# 7.0 it is possible to create functions nested within other functions. This feature is called “local functions”. The following source code shows an according example.

static void Main(string[] args)
{
  int x = DoSomething();

  //int y = Calc(1, 2, 3);  // compiler error because Calc does not exist within this context
}

private static int DoSomething()
{
  int shared = 15;

  int x = Calc(1, 2, 3);

  return x;

  int Calc(int a, int b, int c)
  {
    return a * b + c + shared;
  }
}

 

The function “Calc” is nested within the function “DoSomething”. As a result, “Calc” is available in the function scope only and cannot be used outside of “DoSomething”. Inside the enclosing function, you can place the nested function wherever you want. Furthermore, the nested function has access to the variables of the enclosed function.

In my opinion local functions are a nice feature. A common software design concept is to hide internal implementation details of software components by limiting the scope of internal components. If you have functions which are needed inside a class only, you can limit the scope to this class by defining a private function. But so far this was the lowest possible scope. If you have created sub-functions which were needed by a single function only, you had to publish these functions on class scope too, or use other techniques like lambda functions. With C# 7 you are now able to limit the scope of such nested functions to the scope of the enclosed function.

I like local functions because you are now able to define a proper scope. But there are two features which I don’t like because they may result in source code which could be difficult to understand. One of these two features is the possibility to place the nested functions wherever you want. In my opinion you should place them always at the beginning or at the end. I prefer the end, after the return statement. This will increase the readability of the source code because at first you have the code of the main function itself and then the code of the internal sub-functions. So, you create a clear separation between these different concerns and you have reading order from the enclosed main function to the nested local functions. The second feature which I don’t like is the possibility to access variables of the enclosed function. Based on a technical point of view this feature is fine, because the nested function is part of the enclosing functions scope and therefore can access elements of this scope. But from a software design point of view it is a questionable feature because you can create a lot of dependencies between the enclosed function and several local functions. This can result in difficult code with a spaghetti like data flow. An alternative is to pass variables as parameters to the local functions and create a clear separation between the different functions and their concerns. In my opinion you should therefore use this feature very wisely and only if it really comes along with some advantages.

A typical use case for local functions are recursive function calls. Often you will have a public main function which should do some calculation. This function may now use recursion function calls to execute the calculation. So, you will create an internal function used for these recursive calls and this internal function often contains parameters with interim results or recursion status information. Therefore, such a function should never be part of the public interface. On the contrary, it may only be used meaningful within the scope of the main function. Now you will have the possibility to implement it according to this need and create a local function. The following example shows an according use case. Within a tree structure we will get the maximum depth of the tree.

static void Main(string[] args)
{
  TreeElement tree = new TreeElement();
  TreeElement sub1a = new TreeElement();
  TreeElement sub1b = new TreeElement();
  TreeElement sub2 = new TreeElement();

  sub1b.SubElements = new List();
  sub1b.SubElements.Add(sub2);

  tree.SubElements = new List();
  tree.SubElements.Add(sub1a);
  tree.SubElements.Add(sub1b);

  int depth = GetDepth(tree);
  Console.WriteLine("Depth: " + depth);
}

public class TreeElement
{
  public List SubElements;
}

private static int GetDepth(TreeElement tree)
{
  return GetDepth(tree, 1);

  int GetDepth(TreeElement subTree, int actualDepth)
  {
    int maxDepth = actualDepth;

    if (subTree.SubElements == null)
    {
      return actualDepth;
    }

    foreach (TreeElement element in subTree.SubElements)
    {
      int elementDepth = GetDepth(element, actualDepth + 1);
      if (maxDepth < elementDepth)
      {
        maxDepth = elementDepth;
      }
    }

    return maxDepth;
  }
}

 

The calculation of the depth is done by recursive function calls which step into the sub elements. If a tree leaf is reached, the recursive function will return its current depth. Therefore, the actual depth of the sub-tree is passed as parameter to the recursive function. Of course, you could use the recursive function in the public interface too but in this case the user of the interface will see the depth parameter too which is needed internally only. You could set the default value of this parameter to “1” and explain it within a documentation but nevertheless you interface gets dirty as it contains parameters which are necessary due to internal needs only. By using a local function, you can offer a clean interface and hide the internal implementation details. Furthermore, this internal helper function is visible within the containing function scope only and will therefore neither be visible in the public interface nor in the private interface of the surrounding class.

Werbung
Dieser Beitrag wurde unter C# veröffentlicht. Setze ein Lesezeichen auf den Permalink.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit deinem WordPress.com-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit deinem Facebook-Konto. Abmelden /  Ändern )

Verbinde mit %s