Introduction
This article covers method changes and syntax improvements done in the newer version of C#, which includes local functions, static local function, and default interface methods. This article is the third article of the series, and below is the link for my previous articles. It is highly recommended to go through the previous articles to have a better understanding.
The following is a list of features that we are going to cover in this article:
C# 7.0
Local function or nested function
C# 8.0
- Static local functions
- Default interface methods
C# 9.0*: Proposed Method Enhancement
Permit attributes on local functions (proposed in C# 9.0)
Default Interface Method
In C# 8.0 and onwards, we can create a default interface method, (i.e., a method inside the interface can have an implementation). In other words, we can say that in C# 8.0 and onwards, an interface can contain a concrete method (method implementation) along with abstract methods.
So we can have an interface like shown below:
- interface IUser
- {
- bool AddUser()
- {
- bool isAdded = false;
-
- return isAdded;
- }
- }
And we can call the default interface method through an object as well. Following is the complete code:
- namespace DefaultMethodExample
- {
- class Program
- {
- static void Main()
- {
- IUser user = new User();
- user.AddUser();
- }
- }
-
- interface IUser
- {
-
- bool AddUser()
- {
- bool isAdded = false;
-
- return isAdded;
- }
- }
- class User : IUser
- {
-
-
- }
- }
The above code snippet is just an example of how a default interface method works.
When do we need a Default Interface Method?
Let's take a real scenario where we may need a default interface method.
An interface with some abstract methods:
- interface IUser
- {
- bool AddUser();
- bool DeleteUser();
- string GetUserDetails();
- }
An Existing Client which Implements the IUser interface:
- class ExistingUser : IUser
- {
- public bool AddUser() =>
-
- true;
-
- public bool DeleteUser() =>
-
- true;
-
- public string GetUserDetails()
- {
- string userDetails = string.Empty;
-
- return userDetails;
- }
- }
New Requirement
Add a new Method Inside the existing interface to get the social media details of a user. The new interface will have the following code:
- interface IUser
- {
- bool AddUser();
- bool DeleteUser();
- string GetUserDetails();
- string GetSocialMediaDetails();
- }
It is decided that whoever is a new client who implements IUser can have their own implementation for all the methods. But it will not work because we already have a client who is using the current version of the interface so we cannot add a new method inside the interface; otherwise, no longer the existing client would be able to use it. So, in this case, we need to think about another alternative such as introducing a new interface for new clients or some other techniques. Here the “default interface method” can be one of the best solutions by which we can add a new method inside the interface without disturbing the existing client.
Following is the complete code using a console application:
IUser.cs
- namespace DefaultMethodExample
- {
- interface IUser
- {
- bool AddUser();
- bool DeleteUser();
- string GetUserDetails();
- string GetSocialMediaDetails() => "There is no social media details available for old client.";
- }
- }
ExistingUser.cs
- namespace DefaultMethodExample
- {
-
-
- class ExistingUser : IUser
- {
- public bool AddUser() =>
-
- true;
-
- public bool DeleteUser() =>
-
- true;
-
- public string GetUserDetails()
- {
- string userDetails = string.Empty;
-
- return userDetails;
- }
- }
- }
NewUser.cs
- namespace DefaultMethodExample
- {
- class NewUser : IUser
- {
- public bool AddUser() =>
-
- true;
-
- public bool DeleteUser() =>
-
- true;
-
-
-
- public string GetSocialMediaDetails() =>
-
- "Twitter URL: @....";
-
- public string GetUserDetails()
- {
- string userDetails = string.Empty;
-
- return userDetails;
- }
- }
- }
Program.cs
- using static System.Console;
- namespace DefaultMethodExample
- {
- class Program
- {
- static void Main()
- {
- IUser existingUser = new ExistingUser();
- WriteLine(existingUser.GetSocialMediaDetails());
-
- IUser newUser = new NewUser();
- WriteLine(newUser.GetSocialMediaDetails());
- }
- }
- }
The default interface method would be beneficial when we need to extend an existing interface or if we need to implement versioning for an API service, where we can serve both new and existing clients using the same interface without changing the existing client code.
Local Function (Nested Function)
In the earlier version of C# until C# 6.0, we could have a nested class, but we could not have a nested function. However, from C# 7.0 onwards, we can have nested function as well. This nested function is more commonly referred to as local Function in C#.
The following code is an example of a local function:
- static void Main()
- {
- var result = Sum(5, 7);
- int Sum(int x, int y)
- {
- return x + y;
- }
- }
Many times the question of why we need a local function arises. This is because:
- There are multiple cases where we only need to use a method once. So we can make that method local to make it more readable and clarify the intent.
- In many cases, a local function is far better than a recursive function, as it does not need to maintain the call stack.
Making a local function for single referenced method
Have a look at the below code snippet:
- public class Offer
- {
- public void ShowOfferDeatils()
- {
-
- var couponCode=GetCouponCode();
- Console.WriteLine(couponCode);
- }
-
- private string GetCouponCode() => "Off" + new Random().Next();
-
- }
As we can see in the above code snippet that the function “GetCouponCode()” is being used only once. So we can move it inside the same method from where it is being called. So the above code can be re-written as follows,
- public class Offer
- {
- public void ShowOfferDeatils()
- {
-
- var couponCode = GetCouponCode();
- Console.WriteLine(couponCode);
-
- string GetCouponCode() => "Off" + new Random().Next();
- }
- }
Local function performance over a recursive function
We know that if we call a recursive function, then a call stack is being maintained, In case of a local function, no call stack is being maintained, so it will always be faster than a recursive function call.
Let us have a look at the code below to compare the performance:
- using System.Numerics;
- using System.Diagnostics;
- using static System.Console;
-
- namespace LocalFunctionExample
- {
- class Program
- {
- static void Main()
- {
- int x = 8500;
- Stopwatch watchRecursive = new Stopwatch();
- watchRecursive.Start();
- _ = Factorial(x);
- watchRecursive.Stop();
-
- Stopwatch watchLocal = new Stopwatch();
- watchLocal.Start();
- _ = FactorialLocal(x);
- watchLocal.Stop();
-
- WriteLine($"Time taken to calculate factorial of {x}");
- WriteLine($"Using recursive function: {watchRecursive.Elapsed.TotalMilliseconds}");
- WriteLine($"Using local function: {watchLocal.Elapsed.TotalMilliseconds}");
- }
-
-
- private static BigInteger FactorialLocal(int x)
- {
-
- if (x == 0) return 1;
- BigInteger result = x;
- while (x > 1)
- {
- x--;
- Multiply();
- }
- void Multiply() => result *= x;
- return result;
- }
- private static BigInteger Factorial(int x) =>
-
- x == 0 ? 1 : x * Factorial(x - 1);
- }
- }
In the above screenshot, we can see that a local function call is almost 10 times faster than a recursive function call.
We know that we can calculate the factorial value without using a recursive function as well. But here, my purpose is only to compare the performance difference; that is why we have taken it as an example.
A Few Questions about Local Functions
1. Can I get a Stackoverflow exception due to a local method call?
No. it does not maintain call stack, so we do not get stack overflow exception due to local method call.
2. Can we have an abstract local function?
No, we cannot have an abstract local function.
- static void Main()
- {
- int Sum();
- }
Compilation Error
Error CS8112: 'Sum()' is a local function and must therefore always have a body.
3. Can we have a local function with multiple levels of nesting?
Yes. We can have a local function with multiple levels of nesting. Have a look at the code snippet below:
- class Program
- {
-
- static void Main()
- {
-
- void Calculate(int x, int y)
- {
-
- int Add()
- {
- return x + y;
- }
-
-
- int Diff()
- {
- return x - y;
- }
- }
- }
- }
4. Can we have an access modifier for a local function?
No. Access modifiers, e.g. private, public, protected, internal, private protected, and protected internal access modifiers are not allowed with local function. This is because it will be accessed only by the parent method in which it has been written, so there is no need to provide an accessibility modifier on it.
There are some cases for local functions that we are not going to cover in this article. If you would like to learn more about a local function and C# 7 new features, you can go through the article below.
Static Local Function
The Static Local Function is a new feature introduced with C# 8.0. Before C# 8.0, we could not have a local function as a static function.
In C# 7.x the below code does not get compiled.
- static void Main()
- {
- var result = Sum(5, 7);
- static int Sum(int x, int y)
- {
- return x + y;
- }
- }
Error: CS0106 The modifier 'static' is not valid for this item.
Whereas in C# 8.0, the above code gets compiled successfully. But as we know that there are some conditions with static function, so those conditions also apply with local static function. The last code example we have taken can also be re-written as shown below:
- static void Main()
- {
- int x = 5, y = 7;
- var result = Sum();
- int Sum() => x + y;
- }
So in the above code snippet, we can see that we are using the parent method variable in a local function and these variables are non-static variables. But if we try to put a static keyword with the local function, which is accessing variables of parent method, then it will not work. The following is the screenshot for this principle.
Permit attributes on local functions (proposed in C# 9.0)
We have seen so far that local functions were introduced with C# 7.0. Later on, enhancement is done in C# 8.0 to allow a static variable. But it still has some restrictions.
Until C# 8.0, we could not use attributes with a “local function”.
- using System;
- using System.Diagnostics;
-
- namespace StaticLocalFunctionExample
- {
- class Program
- {
- static void Main()
- {
- Sum(8, 11);
-
- [Conditional("DEBUG")]
- static void Sum(int x, int y)
- {
- Console.WriteLine(x + y);
- }
- }
- }
- }
The above code does not get compiled up to C# 8.0. But in C# 9, there is a proposal to allow attribute with local function.
Summary
In this article, we have covered the following topics:
- Local function or nested function (C# 7.0).
- Making local functions for single referenced method.
- Local function performance over a recursive function.
- Reduce call stack using a local function.
- Static local functions (C# 8.0).
- Default interface methods (C# 8.0).
- When do we need a Default Interface Method?
- Permit attributes on local functions (proposed in C# 9.0).