Funções locais comparadas com expressões lambdaLocal functions compared to lambda expressions

À primeira vista, funções locais e expressões lambda são muito semelhantes.At first glance, local functions and lambda expressions are very similar. Em muitos casos, a escolha entre usar expressões lambda e funções locais é uma questão de estilo e preferência pessoal.In many cases, the choice between using lambda expressions and local functions is a matter of style and personal preference. No entanto, há diferenças reais nos casos em que você pode usar uma ou outra, e é importante conhecer essas diferenças.However, there are real differences in where you can use one or the other that you should be aware of.

Examinaremos as diferenças entre a função local e as implementações de expressão lambda do algoritmo fatorial.Let's examine the differences between the local function and lambda expression implementations of the factorial algorithm. Primeiro a versão usando uma função local:First the version using a local function:

public static int LocalFunctionFactorial(int n)
{
    return nthFactorial(n);

    int nthFactorial(int number) => (number < 2) ? 
        1 : number * nthFactorial(number - 1);
}

Compare essa implementação com uma versão que usa expressões lambda:Contrast that implementation with a version that uses lambda expressions:

public static int LambdaFactorial(int n)
{
    Func<int, int> nthFactorial = default(Func<int, int>);

    nthFactorial = (number) => (number < 2) ? 
        1 : number * nthFactorial(number - 1);

    return nthFactorial(n);
}

As funções locais têm nomes.The local functions have names. As expressões lambda são métodos anônimos que são atribuídos a variáveis dos tipos Func ou Action.The lambda expressions are anonymous methods that are assigned to variables that are Func or Action types. Quando você declara uma função local, os tipos de argumento e o tipo de retorno fazem parte da declaração da função.When you declare a local function, the argument types and return type are part of the function declaration. Em vez de fazer parte do corpo da expressão lambda, os tipos de argumento e o tipo de retorno são parte da declaração de tipo de variável da expressão lambda.Instead of being part of the body of the lambda expression, the argument types and return type are part of the lambda expression's variable type declaration. Essas duas diferenças podem resultar em um código mais claro.Those two differences may result in clearer code.

As funções locais têm diferentes regras para atribuição definida em relação às expressões lambda.Local functions have different rules for definite assignment than lambda expressions. Uma declaração de função local pode ser referenciada em qualquer local do código em que ela esteja no escopo.A local function declaration can be referenced from any code location where it is in scope. Uma expressão lambda precisa ser atribuída a uma variável delegada para que possa ser acessada (ou chamada por meio do delegada que referencia a expressão lambda). Observe que a versão usando a expressão lambda deve declarar e inicializar a expressão lambda, nthFactorial antes de defini-la.A lambda expression must be assigned to a delegate variable before it can be accessed (or called through the delegate referencing the lambda expression.) Notice that the version using the lambda expression must declare and initialize the lambda expression, nthFactorial before defining it. Não fazer isso resulta em um erro em tempo de compilação para referenciar nthFactorial antes de atribuí-lo.Not doing so results in a compile time error for referencing nthFactorial before assigning it. Essas diferenças significam que os algoritmos recursivos são mais fáceis de criar usando funções locais.These differences mean that recursive algorithms are easier to create using local functions. Você pode declarar e definir uma função local que chame a si mesma.You can declare and define a local function that calls itself. As expressões lambda devem ser declaradas e atribuídas a um valor padrão antes que possam ser reatribuídas a um corpo que referencie a mesma expressão lambda.Lambda expressions must be declared, and assigned a default value before they can be re-assigned to a body that references the same lambda expression.

As regras de atribuição definidas também afetam as variáveis que são capturadas pela função local ou pela expressão lambda.Definite assignment rules also affect any variables that are captured by the local function or lambda expression. As regras das funções locais e das expressões lambda exigem que as variáveis capturadas sejam definitivamente atribuídas no momento em que a expressão lambda ou a função local é convertida em um delegado.Both local functions and lambda expression rules demand that any captured variables are definitely assigned at the point when the local function or lambda expression is converted to a delegate. A diferença é que as expressões lambda são convertidas em delegados no momento em que são declaradas.The difference is that lambda expressions are converted to delegates when they are declared. As funções locais são convertidas em delegados somente quando usadas como um delegado.Local functions are converted to delegates only when used as a delegate. Se você declarar uma função local e só referenciá-la ao chamá-la como um método, ela não será convertida em um delegado.If you declare a local function and only reference it by calling it like a method, it will not be converted to a delegate. Essa regra permite que você declare uma função local em qualquer local conveniente no respectivo escopo delimitador.That rule enables you to declare a local function at any convenient location in its enclosing scope. É comum declarar funções locais ao final do método pai, depois das instruções de retorno.It's common to declare local functions at the end of the parent method, after any return statements.

Em terceiro lugar, o compilador pode executar uma análise estática que permite que as funções locais atribuam definitivamente as variáveis capturadas no escopo delimitador.Third, the compiler can perform static analysis that enables local functions to definitely assign captured variables in the enclosing scope. Considere este exemplo:Consider this example:

int M()
{
    int y;
    LocalFunction();
    return y;

    void LocalFunction() => y = 0;
}

O compilador pode determinar que LocalFunction definitivamente atribua y quando chamada.The compiler can determine that LocalFunction definitely assigns y when called. Como a LocalFunction é chamada antes da instrução return, y é atribuído definitivamente na instrução return.Because LocalFunction is called before the return statement, y is definitely assigned at the return statement.

Essa análise de exemplo permite a quarta diferença.The analysis that enables the example analysis enables the fourth difference. Dependendo do uso, as funções locais podem evitar as alocações de heap que são sempre necessárias nas expressões lambda.Depending on their use, local functions can avoid heap allocations that are always necessary for lambda expressions. Se uma função local nunca é convertida em um delegado, e nenhuma das variáveis capturadas pela função local é capturada por outros lambdas ou funções locais que são convertidas em delegados, o compilador pode evitar alocações de heap.If a local function is never converted to a delegate, and none of the variables captured by the local function is captured by other lambdas or local functions that are converted to delegates, the compiler can avoid heap allocations.

Considere este exemplo assíncrono:Consider this async example:

public Task<string> PerformLongRunningWorkLambda(string address, int index, string name)
{
    if (string.IsNullOrWhiteSpace(address))
        throw new ArgumentException(message: "An address is required", paramName: nameof(address));
    if (index < 0)
        throw new ArgumentOutOfRangeException(paramName: nameof(index), message: "The index must be non-negative");
    if (string.IsNullOrWhiteSpace(name))
        throw new ArgumentException(message: "You must supply a name", paramName: nameof(name));

    Func<Task<string>> longRunningWorkImplementation = async () =>
    {
        var interimResult = await FirstWork(address);
        var secondResult = await SecondStep(index, name);
        return $"The results are {interimResult} and {secondResult}. Enjoy.";
    };

    return longRunningWorkImplementation();
}

O fechamento desta expressão lambda contém as variáveis address, index e name.The closure for this lambda expression contains the address, index and name variables. No caso de funções locais, o objeto que implementa o encerramento pode ser um tipo struct.In the case of local functions, the object that implements the closure may be a struct type. Esse tipo de struct seria passado por referência à função local.That struct type would be passed by reference to the local function. Essa diferença na implementação poderia economizar em uma alocação.This difference in implementation would save on an allocation.

A instanciação necessária para expressões lambda ocasiona alocações adicionais de memória, tornando-se um fator de desempenho em caminhos de código com tempo crítico.The instantiation necessary for lambda expressions means extra memory allocations, which may be a performance factor in time-critical code paths. As funções locais não incorrem nessa sobrecarga.Local functions do not incur this overhead. No exemplo acima, a versão das funções locais tem 2 alocações a menos que a versão da expressão lambda.In the example above, the local functions version has 2 fewer allocations than the lambda expression version.

Observação

A função local equivalente desse método também usa uma classe para o fechamento.The local function equivalent of this method also uses a class for the closure. O fechamento de uma função local ser implementado como um class ou como um struct, trata-se de um detalhe de implementação.Whether the closure for a local function is implemented as a class or a struct is an implementation detail. Uma função local pode usar um struct, enquanto uma lambda sempre usará um class.A local function may use a struct whereas a lambda will always use a class.

public Task<string> PerformLongRunningWork(string address, int index, string name)
{
    if (string.IsNullOrWhiteSpace(address))
        throw new ArgumentException(message: "An address is required", paramName: nameof(address));
    if (index < 0)
        throw new ArgumentOutOfRangeException(paramName: nameof(index), message: "The index must be non-negative");
    if (string.IsNullOrWhiteSpace(name))
        throw new ArgumentException(message: "You must supply a name", paramName: nameof(name));

    return longRunningWorkImplementation();

    async Task<string> longRunningWorkImplementation()
    {
        var interimResult = await FirstWork(address);
        var secondResult = await SecondStep(index, name);
        return $"The results are {interimResult} and {secondResult}. Enjoy.";
    }
}

Uma vantagem final não demonstrada neste exemplo é que as funções locais podem ser implementadas como iteradores, usando a sintaxe yield return para produzir uma sequência de valores.One final advantage not demonstrated in this sample is that local functions can be implemented as iterators, using the yield return syntax to produce a sequence of values. A instrução yield return não é permitida em expressões lambda.The yield return statement is not allowed in lambda expressions.

Embora as funções locais possam parecer redundantes para expressões lambda, elas realmente têm finalidades e usos diferentes.While local functions may seem redundant to lambda expressions, they actually serve different purposes and have different uses. As funções locais são mais eficientes para quando você deseja escrever uma função que é chamada apenas do contexto de outro método.Local functions are more efficient for the case when you want to write a function that is called only from the context of another method.