New C# 7 features in action: Local Functions in C# 7
C# 7’s Local Functions
1. Overloading not supported.
2. Accessibility modifier (public, private, protected) not allowed.
3. Compiler will issue a warning if not used
4. All variables in the enclosing scope, including local variables can be accessed: (as shown in example below)
invalidData local variable of IsValidInputDataWithLogging function can be accessed by LogInvalidDataMessages local function.
public static bool IsValidInputDataWithLogging(Dictionary<string, string> inputData) { var output = HasValidValues(); var invalidData = output.Where(x => x.Value.Status == false); // Some Code. LogInvalidDataMessages(); return false; void LogInvalidDataMessages() // LogInvalidDataMessages local function. { foreach (var item in invalidData) // LogInvalidDataMessages can access invalidData defined in the enclosing scope. { // Some Code. } } Dictionary<string, OperationResult> HasValidValues() // HasValidValues local function. { foreach (var inputString in inputData) // HasValidValues can access inputData dictionary. { // Some Code. } } }
5. Are in scope for the entire method in which they are present: (as shown in example below)
public static bool IsValidInputDataWithLogging(Dictionary<string, string> inputData) { var output = HasValidValues(); var invalidData = output.Where(x => x.Value.Status == false); void LogInvalidDataMessages() // LogInvalidDataMessages local function. { // Some Code. } // Some Code. LogInvalidDataMessages(); Dictionary<string, OperationResult> HasValidValues() // HasValidValues local function. { // Some Code. } // Some Code. }
Use Cases for C# 7’s Local Functions
A. Parameter validation scenario
In the below example, RequestIsValid local function is used to validate the parameters of InsertNews function.
public bool InsertNews(News request) { var validationResult = RequestIsValid(); if (validationResult.isValid == false) { Console.Write($"{nameof(validationResult.errorMessage)} : {validationResult.errorMessage}"); return false; } // Some code for inserting the news in database. return true; (bool isValid, string errorMessage) RequestIsValid() { if (request == null) { throw new ArgumentNullException(nameof(request), $"The {nameof(request)} may not be null."); } var lsb = new Lazy<StringBuilder>(); if (string.IsNullOrWhiteSpace(request.Headline)) { lsb.Value.AppendLine($"The {nameof(request)}'s {nameof(request.Headline)} property may not be empty."); } if (string.IsNullOrWhiteSpace(request.Detail)) { lsb.Value.AppendLine($"The {nameof(request)}'s {nameof(request.Detail)} property may not be empty."); } if (request.Id <= 0) { lsb.Value.AppendLine($"The {nameof(request)}'s {nameof(request.Id)} property may not be less than zero."); } if (lsb.IsValueCreated) { var errorMessage = lsb.Value.ToString(); return (isValid: false, errorMessage: errorMessage); } return (isValid: true, errorMessage: string.Empty); } }
B. Iterator functions
In case of iterators functions, non-iterator wrapper public function is commonly needed for eagerly checking the arguments at the time of the call. Local function as best suited in this use case.
public IEnumerable<Book> BooksAvailableForDisplay(IEnumerable<Book> booksAvailableInStock, Func<Book, bool> validate) { if (booksAvailableInStock == null) { throw new ArgumentNullException(nameof(booksAvailableInStock)); } if (validate == null) { throw new ArgumentNullException(nameof(validate)); } return ValidBooksForDisplay(); IEnumerable<Book> ValidBooksForDisplay() { foreach (var book in booksAvailableInStock) { if (validate(book)) { yield return book; } } } }
C. Methods that are called from only one place & only makes sense inside of a single method that uses it
In the below example, LogInvalidDataMessages and HasValidValues local functions are available inside IsValidInputDataWithLogging function. In this case, LogInvalidDataMessages and HasValidValues functions are only used by IsValidInputDataWithLogging function.
public bool IsValidInputDataWithLogging(Dictionary<string, string> inputData) { var output = HasValidValues(); var invalidData = output.Where(x => x.Value.Status== false); if (invalidData.Any() == false) { return true; } LogInvalidDataMessages(); return false; void LogInvalidDataMessages() // LogInvalidDataMessages local function. { // Some Code. } Dictionary<string, OperationResult> HasValidValues() // HasValidValues local function. { // Some Code. } }
Full sample code to explain the use of C# 7’s Local Function
Note the use of Local Functions:
- Parameter validation scenario (RequestIsValid local function inside InsertNews function)
- Iterator functions (ValidBooksForDisplay iterator function)
- Methods that are called from only one place & only makes sense inside of a single method that uses it (LogInvalidDataMessages and HasValidValues local functions inside IsValidInputDataWithLogging function)
in the below sample code:
/// <summary> /// CSharp7Sample. /// </summary> public partial class CSharp7Sample { /// <summary> /// Checks Input Data is valid. /// </summary> /// <param name="inputData">Input data.</param> /// <returns>true or false.</returns> public bool IsValidInputDataWithLogging(Dictionary<string, string> inputData) { var output = HasValidValues(); var invalidData = output.Where(x => x.Value.Status == false); if (invalidData.Any() == false) { return true; } LogInvalidDataMessages(); return false; void LogInvalidDataMessages() // LogInvalidDataMessages local function. { var logMessages = new StringBuilder(); foreach (var item in invalidData) // LogInvalidDataMessages can access invalidData defined in the enclosing scope. { var value = item.Value; logMessages.Append($"{nameof(item.Key)}: {item.Key}, {nameof(item.Value)}: {item.Value}, {nameof(value.ErrorCode)}: {value.ErrorCode}, {nameof(value.ErrorMessage)}: {value.ErrorMessage}"); logMessages.AppendLine(); } Console.Write(logMessages.ToString()); } Dictionary<string, OperationResult> HasValidValues() // HasValidValues local function. { var result = new Dictionary<string, OperationResult>(); foreach (var inputString in inputData) // HasValidValues can access inputData dictionary. { result.Add(inputString.Key, this.HasValue(inputString.Value)); } return result; } } /// <summary> /// Insert News. /// </summary> /// <param name="request">Request.</param> /// <returns>Status: true or false.</returns> public bool InsertNews(News request) { var validationResult = RequestIsValid(); if (validationResult.isValid == false) { Console.Write($"{nameof(validationResult.errorMessage)} : {validationResult.errorMessage}"); return false; } // Some code for inserting the news in database. return true; (bool isValid, string errorMessage) RequestIsValid() { if (request == null) { throw new ArgumentNullException(nameof(request), $"The {nameof(request)} may not be null."); } var lsb = new Lazy<StringBuilder>(); if (string.IsNullOrWhiteSpace(request.Headline)) { lsb.Value.AppendLine($"The {nameof(request)}'s {nameof(request.Headline)} property may not be empty."); } if (string.IsNullOrWhiteSpace(request.Detail)) { lsb.Value.AppendLine($"The {nameof(request)}'s {nameof(request.Detail)} property may not be empty."); } if (request.Id <= 0) { lsb.Value.AppendLine($"The {nameof(request)}'s {nameof(request.Id)} property may not be less than zero."); } if (lsb.IsValueCreated) { var errorMessage = lsb.Value.ToString(); return (isValid: false, errorMessage: errorMessage); } return (isValid: true, errorMessage: string.Empty); } } /// <summary> /// Books available for display. /// </summary> /// <param name="booksAvailableInStock">Books available in stock.</param> /// <param name="validate">Validate function.</param> /// <returns>Valid books for display.</returns> public IEnumerable<Book> BooksAvailableForDisplay(IEnumerable<Book> booksAvailableInStock, Func<Book, bool> validate) { if (booksAvailableInStock == null) { throw new ArgumentNullException(nameof(booksAvailableInStock)); } if (validate == null) { throw new ArgumentNullException(nameof(validate)); } return ValidBooksForDisplay(); IEnumerable<Book> ValidBooksForDisplay() { foreach (var book in booksAvailableInStock) { if (validate(book)) { yield return book; } } } } /// <summary> /// Is valid book for display. /// </summary> /// <param name="book">Book.</param> /// <returns>Valid: true or false.</returns> public bool IsValidBookForDisplay(Book book) { if (book == null || string.IsNullOrWhiteSpace(book.Publisher) || string.IsNullOrWhiteSpace(book.BookCategory) || string.IsNullOrWhiteSpace(book.Title) || book.Id <= 0 || book.Price <= 0) { return false; } return true; } /// <summary> /// Input string has value or not. /// </summary> /// <param name="inputString">Input string.</param> /// <returns>Operation Result.</returns> public OperationResult HasValue(string inputString) { var result = new OperationResult {}; if (inputString == null) { result.ErrorCode = 1; result.ErrorMessage = "Input string is null"; } else if (inputString.Equals(string.Empty)) { result.ErrorCode = 2; result.ErrorMessage = "Input string is empty"; } else if (inputString.Trim().Equals(string.Empty)) { result.ErrorCode = 3; result.ErrorMessage = "Input string only whitespaces"; } result.Status = string.IsNullOrEmpty(result.ErrorMessage); return result; } } /// <summary> /// OperationResult. /// </summary> public class OperationResult { /// <summary> /// Gets or sets a value indicating whether status is true. /// </summary> public bool Status { get; set; } /// <summary> /// Gets or sets the error code. /// </summary> public int ErrorCode { get; set; } /// <summary> /// Gets or sets the error message. /// </summary> public string ErrorMessage { get; set; } } /// <summary> /// Book class. /// </summary> public class Book { public string Publisher { get; set; } public string BookCategory { get; set; } public int Id { get; set; } public string Title { get; set; } public decimal Price { get; set; } } /// <summary> /// News class. /// </summary> public class News { public int Id { get; set; } public string Headline { get; set; } public string Detail { get; set; } }
Happy Coding !!!
.NET Professional | Microsoft Certified Professional | DZone’s Most Valuable Blogger | Web Developer | Author | Blogger
Doctorate in Computer Science and Engineering
Microsoft Certified Professional (MCP) with over 12+ years of software industry experience including development, implementation & deployment of applications in the .NET framework
Experienced and skilled Agile Developer with a strong record of excellent teamwork, successful coding & project management. Specialises in problem identification and proposal of alternative solutions. Provided knowledge and individual mentoring to team members as needed
Among top 3% overall in terms of contribution on Stack Overflow (~2.3 million people reached my posts). Part of the top 1% Stack Overflow answerers in ASP.NET technology.
DZone’s Most Valuable Blogger (MVB)
Created and actively maintain the TechCartNow.com tech blog while also editing, writing, and researching topics for publication.
Excellent skills in Application Development using C#/Vb.Net, .NET Framework, ASP.NET, MVC, ADO.NET, WCF, WPF, Web API, SQL Server, jQuery, Angular, React, BackboneJS