Confronto tra funzioni locali ed espressioni lambdaLocal functions compared to lambda expressions

A prima vista, le funzioni locali e le espressioni lambda sono molto simili.At first glance, local functions and lambda expressions are very similar. In molti casi, la scelta tra l'uso di funzioni locali ed espressioni lambda è una questione di stile e preferenze personali.In many cases, the choice between using lambda expressions and local functions is a matter of style and personal preference. Esistono tuttavia differenze reali di cui è necessario essere consapevoli nei casi in cui è possibile usare le une o le altre.However, there are real differences in where you can use one or the other that you should be aware of.

Si esamineranno ora le differenze tra implementazioni di funzioni locali e implementazioni di espressioni lambda dell'algoritmo fattoriale.Let's examine the differences between the local function and lambda expression implementations of the factorial algorithm. Si analizzerà per prima la versione che usa una funzione locale: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);
}

Confrontare l'implementazione con una versione che usa le espressioni 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);
}

Le funzioni locali hanno nomi.The local functions have names. Le espressioni lambda sono metodi anonimi che vengono assegnati a variabili di tipo Func o Action.The lambda expressions are anonymous methods that are assigned to variables that are Func or Action types. Quando si dichiara una funzione locale, i tipi di argomento e il tipo restituito fanno parte della dichiarazione della funzione.When you declare a local function, the argument types and return type are part of the function declaration. Anziché far parte del corpo dell'espressione lambda, i tipi di argomento e il tipo restituito fanno parte della dichiarazione del tipo di variabile dell'espressione 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. Queste due differenze possono avere come risultato una maggiore chiarezza del codice.Those two differences may result in clearer code.

Le funzioni locali hanno regole diverse per l'assegnazione certa rispetto alle espressioni lambda.Local functions have different rules for definite assignment than lambda expressions. A una dichiarazione di funzione locale si può fare riferimento da qualsiasi posizione di codice nel cui ambito la funzione si trova.A local function declaration can be referenced from any code location where it is in scope. Un'espressione lambda deve essere assegnata a una variabile delegata prima che sia possibile accedervi o che sia possibile chiamarla tramite il delegato che fa riferimento all'espressione lambda stessa. Si noti che la versione che usa l'espressione lambda deve dichiarare e inizializzare l'espressione lambda nthFactorial prima di definirla.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. In caso contrario, si verifica un errore di compilazione per fare riferimento a nthFactorial prima di assegnarla.Not doing so results in a compile time error for referencing nthFactorial before assigning it. Queste differenze fanno sì che gli algoritmi ricorsivi sino più facili da creare usando funzioni locali.These differences mean that recursive algorithms are easier to create using local functions. È possibile dichiarare e definire una funzione locale che chiama se stessa.You can declare and define a local function that calls itself. Le espressioni lambda devono essere dichiarate e a queste deve essere assegnato un valore predefinito prima che sia possibile riassegnarle a un corpo che fa riferimento alla stessa espressione 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.

Le regole di assegnazione certa influiscono anche su tutte le variabili acquisite dalla funzione locale o dall'espressione lambda.Definite assignment rules also affect any variables that are captured by the local function or lambda expression. Sia le regole delle funzioni locali che quelle delle espressioni lambda richiedono che tutte le variabili acquisite siano assegnate in modo certo nel momento in cui la funzione locale o l'espressione lambda viene convertita in delegato.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. La differenza è che le espressioni lambda vengono convertite in delegati quando vengono dichiarate.The difference is that lambda expressions are converted to delegates when they are declared. Le funzioni locali vengono convertite in delegati solo quando vengono usate come delegati.Local functions are converted to delegates only when used as a delegate. Se si dichiara una funzione locale e si fa riferimento a questa solo chiamandola come un metodo, non verrà convertita in delegato.If you declare a local function and only reference it by calling it like a method, it will not be converted to a delegate. Tale regola consente di dichiarare una funzione locale in qualsiasi posizione comoda all'interno dell'ambito che la comprende.That rule enables you to declare a local function at any convenient location in its enclosing scope. È una pratica comune dichiarare funzioni locali alla fine del metodo padre, dopo le istruzioni Return.It's common to declare local functions at the end of the parent method, after any return statements.

In terzo luogo, il compilatore può eseguire un'analisi statica che consente alle funzioni locali di assegnare in modo certo variabili acquisite nell'ambito che le contiene.Third, the compiler can perform static analysis that enables local functions to definitely assign captured variables in the enclosing scope. Si consideri l'esempio seguente:Consider this example:

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

    void LocalFunction() => y = 0;
}

Il compilatore può determinare che LocalFunction assegna in modo certo y quando viene chiamata.The compiler can determine that LocalFunction definitely assigns y when called. Poiché LocalFunction viene chiamata prima dell'istruzione return, y viene assegnata in modo certo in corrispondenza dell'istruzione return.Because LocalFunction is called before the return statement, y is definitely assigned at the return statement.

L'analisi che consente l'analisi di esempio consente la quarta differenza.The analysis that enables the example analysis enables the fourth difference. A seconda del loro uso, le funzioni locali possono evitare le allocazioni di heap, che sono sempre necessarie per le espressioni lambda.Depending on their use, local functions can avoid heap allocations that are always necessary for lambda expressions. Se una funzione locale non viene mai convertita in delegato e nessuna delle variabili acquisite dalla funzione locale viene acquisita da altre espressioni lambda o funzioni locali convertite in delegati, il compilatore può evitare allocazioni di 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.

Si consideri questo esempio asincrono: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();
}

La chiusura per questa espressione lambda contiene le variabili address, index e name.The closure for this lambda expression contains the address, index and name variables. Nel caso delle funzioni locali, l'oggetto che implementa la chiusura può essere di tipo structIn the case of local functions, the object that implements the closure may be a struct type. Tale tipo struct verrebbe passato per riferimento alla funzione locale.That struct type would be passed by reference to the local function. Questa differenza di implementazione consentirebbe di risparmiare un'allocazione.This difference in implementation would save on an allocation.

La creazione di istanze necessaria per le espressioni lambda comporta allocazioni di memoria aggiuntive che possono ridurre le prestazioni nei percorsi di codice in cui il tempo è un fattore cruciale.The instantiation necessary for lambda expressions means extra memory allocations, which may be a performance factor in time-critical code paths. Questo sovraccarico non si verifica per le funzioni locali.Local functions do not incur this overhead. Nell'esempio precedente, la versione con funzioni locali presenta due allocazioni in meno rispetto alla versione con espressioni lambda.In the example above, the local functions version has 2 fewer allocations than the lambda expression version.

Nota

La funzione locale equivalente di questo metodo usa anche una classe per la chiusura.The local function equivalent of this method also uses a class for the closure. Se la chiusura di una funzione locale viene implementata come class o struct non ha molta importanza.Whether the closure for a local function is implemented as a class or a struct is an implementation detail. Una funzione locale può usare struct mentre un'espressione lambda userà sempre 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.";
    }
}

Come ultimo vantaggio, non illustrato in questo esempio, le funzioni locali possono essere implementate come iteratori usando la sintassi yield return per produrre una sequenza di valori.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. L'istruzione yield return non è consentita nelle espressioni lambda.The yield return statement is not allowed in lambda expressions.

Sebbene le funzioni locali possano apparire ridondanti rispetto alle espressioni lambda, in realtà hanno finalità e usi diversi.While local functions may seem redundant to lambda expressions, they actually serve different purposes and have different uses. Le funzioni locali sono più efficienti nel caso si voglia scrivere una funzione che viene chiamata solo dal contesto di un altro metodo.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.