Lokale Funktionen im Vergleich zu LambdaausdrückenLocal functions compared to lambda expressions

Auf den ersten Blick sind lokale Funktionen und Lambdaausdrücke sehr ähnlich.At first glance, local functions and lambda expressions are very similar. In vielen Fällen ist die Entscheidung zwischen Lamdaausdrücken und lokalen Funktionen eine Frage des Formats und persönlicher Präferenz.In many cases, the choice between using lambda expressions and local functions is a matter of style and personal preference. Es gibt allerdings tatsächliche Unterschiede, wann das eine oder das andere verwendet werden kann. Diese sollten Ihnen bekannt sein.However, there are real differences in where you can use one or the other that you should be aware of.

Sehen wir uns die Unterschiede zwischen der Implementierungen des Fakultätsalgorithmus als lokale Funktion und als Lambdaausdruck an.Let's examine the differences between the local function and lambda expression implementations of the factorial algorithm. Erste die Version mit einer lokalen Funktion: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);
}

Vergleichen Sie diese Implementierung mit einer Version, die Lambdaausdrücke verwendet: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);
}

Lokale Funktionen haben Namen.The local functions have names. Lamdaausdrücke sind anonyme Methoden, die Variablen zugewiesen werden, die Func- und Action-Typen sind.The lambda expressions are anonymous methods that are assigned to variables that are Func or Action types. Wenn Sie eine lokale Funktion deklarieren, sind die Argumenttypen und der Rückgabetyp Teil der Funktionsdeklaration.When you declare a local function, the argument types and return type are part of the function declaration. Statt Teil des Texts des Lambdaausdrucks zu sein, sind die Argumentttypen und der Rückgabetyp Teil der Variablentypdeklaration des Lambdaausdrucks.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. Diese beiden Unterschiede können zu klarerem Code führen.Those two differences may result in clearer code.

Lokale Funktionen haben andere Regeln für definitive Zuweisungen als Lambdaausdrücke.Local functions have different rules for definite assignment than lambda expressions. Auf eine Deklaration einer lokalen Funktion kann von jeder Stelle im Code aus, die sich innerhalb des Bereichs befindet, verwiesen werden.A local function declaration can be referenced from any code location where it is in scope. Ein Lambdaausdruck muss einer Delegatvariablen zugewiesen werden, bevor darauf zugegriffen werden kann (oder bevor er aufgerufen werden kann, indem der Delegat auf den Lambdaausdruck verweist). Beachten Sie, dass die Version mit Lambdaausdrücken den Lambdaausdruck nthFactorial deklarieren und initialisieren muss, bevor er definiert wird.A lambda expression must be assigned to a delegate variable before it can be accessed (or called through the delgate referencing the lambda expression.) Notice that the version using the lambda expression must declare and initialize the lambda expression, nthFactorial before defining it. Wird das nicht gemacht, führt dies zu einem Kompilierzeitfehler, weil auf nthFactorial verwiesen wurde, bevor es zugewiesen wurde.Not doing so results in a compile time error for referencing nthFactorial before assigning it. Diese Unterschiede bedeuten, dass rekursive Algorithmen mit lokalen Funktionen leichter erstellt werden können.These differences mean that recursive algorithms are easier to create using local functions. Sie können eine lokale Funktion deklarieren und definieren, die sich selbst aufruft.You can declare and define a local function that calls itself. Lambdaausdrücke müssen deklariert werden, und dann muss ihnen ein Standardwert zugewiesen werden, bevor sie erneut einem Text zugewiesen werden können, der auf den gleichen Lambdaausdruck verweist.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.

Regeln für definitive Zuweisungen gelten auch für Variablen, die von der lokalen Funktion oder dem Lambdaausdruck erfasst werden.Definite assignment rules also affect any variables that are captured by the local function or lamdba epression. Sowohl Regeln für lokale Funktionen als auch für Lambdaausdrücke erfordern, dass alle erfassten Variablen dann definitiv zugewiesen werden, wenn die lokale Funktion oder der Lambdaausdruck in einen Delegaten konvertiert wird.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. Der Unterschied besteht darin, dass Lambdaausdrücke in Delegate konvertiert werden, wenn sie deklariert werden.The difference is that lambda expressions are converted to delegates when they are declared. Lokale Funktionen werden nur dann in Delegate konvertiert, wenn sie als Delegate verwendet werden.Local functions are converted to delegates only when used as a delegate. Wenn Sie eine lokale Funktion deklarieren und nur darauf verweisen, indem Sie sie wie eine Methode aufrufen, wird sie nicht in einen Delegaten konvertiert.If you declare a local function and only reference it by calling it like a method, it will not be converted to a delegate. Durch diese Regel können Sie eine lokale Funktion an jeder passenden Stelle in ihrem einschließenden Bereich deklarieren.That rule enables you to declare a local function at any convenient location in its enclosing scope. Es ist üblich, lokale Funktionen am Ende der übergeordneten Methode hinter allen Rückgabeanweisungen zu deklarieren.It's common to declare local functions at the end of the parent method, after any return statements.

Zudem kann der Compiler statische Analysen durchführen, mit denen lokale Funktionen erfasste Variablen im einschließenden Bereich definitiv zuweisen können.Third, the compiler can perform static analysis that enables local functions to definitely assign captured variables in the enclosing scope. Betrachten Sie das folgende Beispiel:Consider this example:

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

    void LocalFunction() => y = 0;
}

Der Compiler kann festlegen, dass LocalFunction y bei Aufruf definitiv zuweist.The compiler can determine that LocalFunction definitely assigns y when called. Da LocalFunction vor der return-Anweisung aufgerufen wird, wird y definitiv bei der return-Anweisung zugewiesen.Because LocalFunction is called before the return statement, y is definitiely assigned at the return statement.

Die Analyse, die diese Beispielanalyse ermöglicht, ist der vierte Unterschied.The analysis that enables the example analysis enables the fourth difference. Je nach Verwendung können lokale Funktionen Heapzuweisungen vermeiden, die immer für Lambdaausdrücke erforderlich sind.Depending on their use, local functions can avoid heap allocations that are always necessary for lambda expressions. Wenn eine lokale Funktion nie in einen Delegaten konvertiert wird und keine der von der lokalen Funktion erfassten Variablen von anderen Lambdaausdrücken oder lokalen Funktionen erfasst wird, die in Delegate konvertiert werden, kann der Compiler Heapzuweisungen vermeiden.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.

Betrachten Sie das folgende asynchrone Beispiel: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();
}

Der Abschluss dieses Lambdaausdrucks enthält die Variablen address, index und name.The closure for this lambda expression contains the address, index and name variables. Im Fall von lokalen Funktionen ist das Objekt, das den Abschluss implementiert, möglicherweise vom Typ struct.In the case of local functions, the object that implements the closure may be a struct type. Dieser struct-Typ würde per Verweis an die lokale Funktion übergeben.That struct type would be passed by reference to the local function. Dieser Unterschied bei der Implementierung würde bei einer Zuweisung gespart.This difference in implementation would save on an allocation.

Die für Lambdaausdrücke erforderliche Instanziierung bedeutet zusätzliche Speicherbelegung, was ein Leistungsfaktor in zeitkritischen Codepfaden sein kann.The instantiation necessary for lambda expressions means extra memory allocations, which may be a performance factor in time-critical code paths. Lokale Funktionen erfordern diesen Mehraufwand nicht.Local functions do not incur this overhead. Im obigen Beispiel hat die Version mit der lokalen Funktion zwei Zuordnungen weniger als die Version mit dem Lambdaausdruck.In the example above, the local functions version has 2 fewer allocations than the lambda expression version.

Hinweis

Die Entsprechung dieser Methode mit der lokalen Funktion verwendet auch eine Klasse für den Abschluss.The local function equivalent of this method also uses a class for the closure. Ob der Abschluss für eine lokale Funktion als class oder struct implementiert wird, ist ein Implementierungsdetail.Whether the closure for a local function is implemented as a class or a struct is an implementation detail. Eine lokale Funktion verwendet möglicherweise struct, während ein Lambdaausdruck immer class nutzt.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.";
    }
}

Eine letzter Vorteil, der in diesem Beispiel zu kurz gekommen ist, besteht darin, dass lokale Funktionen mithilfe der yield return-Syntax als Iteratoren implementiert werden können, um eine Sequenz von Werten zu erzeugen.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. Die yield return-Anweisung ist in Lambdaausdrücken unzulässig.The yield return statement is not allowed in lambda expressions.

Während lokale Funktionen für Lambdaausdrücke als überflüssig erscheinen, dienen sie tatsächlich anderen Zwecken und haben unterschiedliche Verwendungen.While local functions may seem redundant to lambda expressions, they actually serve different purposes and have different uses. Lokale Funktionen sind effizienter, im Fall dass Sie eine Funktion schreiben möchten, die nur aus dem Kontext einer anderen Methode abgerufen wird.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.