Local Functions, a C# 7 Feature
C# 7 has introduced a number of features which you can explore HERE; In this article we are going to take a look at Local Functions and why you may want to use them.
What are they?
Local Functions enable the definition of functions inside other functions. You can think of them as helper functions that would only make sense within a given context. Such context could be a function, constructor or a property getter or setter. They are transformed by the compiler to private methods meaning there is no overhead in calling them.
The main goal of Local Functions is encapsulation so the compiler is enforcing that such functions cannot be called from anywhere else in the class. Consider the following overly simplified example:
public string Address
{
get
{
return $@"{GetHouseNumber()}, {GetStreet()},
{GetCity()},
{GetCountry()}";
int GetHouseNumber() => 10;
string GetStreet() => "Liberty St";
string GetCity() => "Amsterdam";
string GetCountry() => "Jamaica";
}
}
Here we have a getter property which is composing an address comprised of 4 different segments. Each segment is being retrieved via a separate method, we are also taking advantage of the Expression Bodied and String Interpolation syntax introduced as part of C# 6 to keep our property clear and condensed.
Had we declared those methods as private, they could be invoked by any member in the class so by using Local Functions we have encapsulated their usage.
But we could do this with Lambdas, no?
Yes, if you ignore the constraint forcing us to declare our lambdas before using them, you could technically achieve the same result:
public string Address
{
get
{
Func<int> getHouseNumber = () => 10;
Func<string> getStreet = () => "Liberty St";
Func<string> getCity = () => "Amsterdam";
Func<string> getCountry = () => "Jamaica";
return $@"{getHouseNumber()}, {getStreet()},
{getCity()},
{getCountry()}";
}
}
BUT, doing so is not such a good idea for the following reasons:
- Allocation, By declaring those lambdas you are essentially allocating objects for the delegates on the heap so if your property is being invoked many times then you will end up putting non trivial unnecessary pressure on the GC.
- No support for
ref
,out
,params
or Optional Parameters. - No recursion, in order to be able to call a Lambda recursively you would need to declare it in 2 steps which does not seem elegant to me:
private uint GetFactorial(uint number)
{
var factorial = default(Func<uint, uint>);
factorial = n =>
{
if (n < 2) { return 1; }
return n * factorial(n - 1);
};
return factorial(number);
}
Limitations
Local Functions can be generic, asynchronous, dynamic and they can even have access to variables accessible in their enclosing scope however despite their flexibility they come with the following limitations:
- Cannot be static, you cannot declare a Local Function as static. (no longer true as of C# 8)
- No attributes allowed, Unlike normal methods you cannot use attributes inside a Local Function. This means you cannot use Caller Information so the following example does not compile:
private void Greet()
{
string GetCaller([CallerMemberName] string name = null) => name;
Console.WriteLine("Hello " + GetCaller());
}