Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

New C# 7 Features in Action: Local Functions in C# 7

DZone's Guide to

New C# 7 Features in Action: Local Functions in C# 7

We take a look at the local functions that are new to C# 7.0 and provide several use cases of how to use them in your web applications.

· Web Dev Zone
Free Resource

Tips, tricks and tools for creating your own data-driven app, brought to you in partnership with Qlik.

C# 7's Local Functions

  1. Overloading not supported.

  2. Accessibility modifier (public, private, protected) not allowed.

  3.  The compiler will issue a warning if not used.

  4. All variables in the enclosing scope, including local variables, can be accessed (as shown in the example below).

The invalidData local variable of the IsValidInputDataWithLogging  function can be accessed by the 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. Local functions are in scope for the entire method in which they are present (as shown in the 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, the RequestIsValid local function is used to validate the parameters of the 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 iterator functions, a non-iterator wrapper public function is commonly needed for eagerly checking the arguments at the time of the call. A 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 and Only Make Sense Inside of a Single Method That Uses It

In the below example, the LogInvalidDataMessages and HasValidValues local functions are available inside the IsValidInputDataWithLogging function. In this case, the LogInvalidDataMessages and HasValidValues functions are only used by the 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:

  1. Parameter validation scenario (the RequestIsValid local function inside the InsertNews function)
  2. Iterator functions (like the ValidBooksForDisplay iterator function)
  3. Methods that are called from only one place and only makes sense inside of a single method that uses it (LogInvalidDataMessages and HasValidValues local functions inside the IsValidInputDataWithLogging function)

You can see these at work 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!

Explore data-driven apps with less coding and query writing, brought to you in partnership with Qlik.

Topics:
local functions ,web dev ,c# 7.0

Published at DZone with permission of Kapil Khandelwal, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}