Fonctions locales comparées aux expressions lambdaLocal functions compared to lambda expressions

À première vue, les fonctions locales et les expressions lambda sont très similaires.At first glance, local functions and lambda expressions are very similar. Souvent, le choix d’utiliser des expressions lambda ou des fonctions locales est une question de style et de préférences personnelles.In many cases, the choice between using lambda expressions and local functions is a matter of style and personal preference. Toutefois, il existe de réelles différences qui vous feront utiliser les unes ou les autres et que vous devez connaître.However, there are real differences in where you can use one or the other that you should be aware of.

Examinons les différences entre l’implémentation de l’algorithme factoriel avec une fonction locale et une expression lambda.Let's examine the differences between the local function and lambda expression implementations of the factorial algorithm. Voici tout d’abord la version utilisant une fonction 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);
}

Comparez cette implémentation avec une version qui utilise des expressions 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);
}

Les fonctions locales ont des noms.The local functions have names. Les expressions lambda sont des méthodes anonymes qui sont affectées aux variables de type Func ou Action.The lambda expressions are anonymous methods that are assigned to variables that are Func or Action types. Lorsque vous déclarez une fonction locale, les types d’arguments et le type de retour font partie de la déclaration de fonction.When you declare a local function, the argument types and return type are part of the function declaration. Au lieu de faire partie du corps de l’expression lambda, les types d’arguments et le type de retour font partie de la déclaration de type de variable de l’expression 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. Ces deux différences peuvent rendre le code plus clair.Those two differences may result in clearer code.

Les fonctions locales ont des règles différentes pour l’affectation définie par rapport aux expressions lambda.Local functions have different rules for definite assignment than lambda expressions. Une déclaration de fonction locale peut être référencée à partir de n’importe quel emplacement de code où elle est dans la portée.A local function declaration can be referenced from any code location where it is in scope. Une expression lambda doit être affectée à une variable de délégué avant de pouvoir être accessible (ou appelée par le biais du délégué référençant l’expression lambda). Notez que la version utilisant l’expression lambda doit déclarer et initialiser l’expression lambda nthFactorial avant de la définir.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. Si ce n’est pas le cas, cela entraîne une erreur de compilation due au fait que vous référencez nthFactorial avant de lui affecter une valeur.Not doing so results in a compile time error for referencing nthFactorial before assigning it. Ces différences font que les algorithmes récursifs sont plus faciles à créer en utilisant des fonctions locales.These differences mean that recursive algorithms are easier to create using local functions. Vous pouvez déclarer et définir une fonction locale qui s’appelle elle-même.You can declare and define a local function that calls itself. Les expressions lambda doivent être déclarées et une valeur par défaut doit leur être affectée avant qu’elles puissent être réaffectées à un corps référençant la même expression 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.

Les règles d’affectation définies s’appliquent également à toutes les variables qui sont capturées par la fonction locale ou l’expression lambda.Definite assignment rules also affect any variables that are captured by the local function or lambda expression. Les règles des fonctions locales comme celles des expression lambda exigent que toutes les variables capturées soient définitivement affectées au point marquant le moment où la fonction locale ou l’expression lambda est convertie en délégué.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 différence est que les expressions lambda sont converties en délégués au moment où elles sont déclarées.The difference is that lambda expressions are converted to delegates when they are declared. Les fonctions locales sont converties en délégués uniquement lorsqu’elles sont utilisées en tant que délégué.Local functions are converted to delegates only when used as a delegate. Si vous déclarez une fonction locale et la référencez uniquement en l’appelant comme une méthode, elle ne sera pas convertie en délégué.If you declare a local function and only reference it by calling it like a method, it will not be converted to a delegate. Cette règle vous permet de déclarer une fonction locale à n’importe quel emplacement qui vous convient dans sa portée englobante.That rule enables you to declare a local function at any convenient location in its enclosing scope. Il est courant de déclarer des fonctions locales à la fin de la méthode parente, après des instructions return.It's common to declare local functions at the end of the parent method, after any return statements.

Troisième différence, le compilateur peut effectuer une analyse statique qui active des fonctions locales de manière à affecter définitivement les variables capturées dans la portée englobante.Third, the compiler can perform static analysis that enables local functions to definitely assign captured variables in the enclosing scope. Considérez cet exemple :Consider this example:

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

    void LocalFunction() => y = 0;
}

Le compilateur peut déterminer que LocalFunction affecte y de manière définitive lorsqu’elle est appelée.The compiler can determine that LocalFunction definitely assigns y when called. Dans la mesure où LocalFunction est appelée avant l’instruction return, y est affecté de manière définitive à l’instruction return.Because LocalFunction is called before the return statement, y is definitely assigned at the return statement.

L’analyse qui active l’exemple d’analyse constitue la quatrième différence.The analysis that enables the example analysis enables the fourth difference. En fonction de leur utilisation, les fonctions locales peuvent éviter les allocations de tas qui sont toujours nécessaires pour les expressions lambda.Depending on their use, local functions can avoid heap allocations that are always necessary for lambda expressions. Si une fonction locale n’est jamais convertie en délégué, et qu’aucune des variables capturées par la fonction locale n’est capturée par d’autres expressions lambda ou fonctions locales qui sont converties en délégués, le compilateur peut éviter les allocations de tas.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.

Penchons-nous sur cet exemple asynchrone :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 fermeture de cette expression lambda contient les variables address, index et name.The closure for this lambda expression contains the address, index and name variables. Dans le cas des fonctions locales, l’objet qui implémente la fermeture peut être un type struct.In the case of local functions, the object that implements the closure may be a struct type. Ce type de struct serait transmis par référence à la fonction locale.That struct type would be passed by reference to the local function. Cette différence d’implémentation évite une allocation.This difference in implementation would save on an allocation.

L’instanciation nécessaire pour les expressions lambda signifie des allocations de mémoire supplémentaires, qui peuvent être un facteur influençant les performances dans les chemins de code critiques au niveau du temps.The instantiation necessary for lambda expressions means extra memory allocations, which may be a performance factor in time-critical code paths. Les fonctions locales n’entraînent pas cette charge supplémentaire.Local functions do not incur this overhead. Dans l’exemple ci-dessus, la version à fonction locale a 2 allocations de moins que la version à expression lambda.In the example above, the local functions version has 2 fewer allocations than the lambda expression version.

Notes

L’équivalent de cette méthode avec une fonction locale fait aussi appel à une classe pour la fermeture.The local function equivalent of this method also uses a class for the closure. Le fait que la fermeture d’une fonction locale soit implémentée en tant que class ou struct est un détail d’implémentation.Whether the closure for a local function is implemented as a class or a struct is an implementation detail. Une fonction locale peut utiliser un type struct contrairement à une expression lambda qui utilise toujours un type 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.";
    }
}

Ultime avantage non décrit dans cet exemple : les fonctions locales peuvent être implémentées en tant qu’itérateurs, en utilisant la syntaxe yield return pour produire une séquence de valeurs.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’instruction yield return n’est pas autorisée dans les expressions lambda.The yield return statement is not allowed in lambda expressions.

Alors que les fonctions locales peuvent sembler redondantes par rapport aux expressions lambda, elles ont en réalité des objectifs différents et des utilisations différentes.While local functions may seem redundant to lambda expressions, they actually serve different purposes and have different uses. Les fonctions locales sont plus efficaces dans le cas où vous voulez écrire une fonction qui est appelée seulement dans le contexte d’une autre méthode.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.