Lambda-expressies en anonieme functies

U gebruikt een lambda-expressie om een anonieme functie te maken. Gebruik de operator => lambda-declaratie om de parameterlijst van lambda te scheiden van de hoofdtekst. Een lambda-expressie kan een van de volgende twee vormen hebben:

  • Expressie lambda met een expressie als hoofdtekst:

    (input-parameters) => expression
    
  • Instructie lambda met een instructieblok als hoofdtekst:

    (input-parameters) => { <sequence-of-statements> }
    

Als u een lambda-expressie wilt maken, geeft u invoerparameters (indien aanwezig) aan de linkerkant van de lambda-operator en een expressie of een instructieblok aan de andere kant op.

Elke lambda-expressie kan worden geconverteerd naar een gemachtigde . Het gemachtigde type waarnaar een lambda-expressie kan worden geconverteerd, wordt gedefinieerd door de typen parameters en retourwaarde. Als een lambda-expressie geen waarde retourneert, kan deze worden geconverteerd naar een van de Action gedelegeerdentypen. Anders kan deze worden geconverteerd naar een van de Func gedelegeerdentypen. Een lambda-expressie met twee parameters en retourneert bijvoorbeeld geen waarde die kan worden geconverteerd naar een Action<T1,T2> gemachtigde. Een lambda-expressie met één parameter en retourneert een waarde kan worden geconverteerd naar een Func<T,TResult> gemachtigde. In het volgende voorbeeld wordt de lambda-expressie x => x * x, die een parameter met de naam en retourneert x de waarde van x kwadraten, toegewezen aan een variabele van een gemachtigdetype:

Func<int, int> square = x => x * x;
Console.WriteLine(square(5));
// Output:
// 25

Expressie lambdas kan ook worden geconverteerd naar de expressiestructuurtypen , zoals in het volgende voorbeeld wordt weergegeven:

System.Linq.Expressions.Expression<Func<int, int>> e = x => x * x;
Console.WriteLine(e);
// Output:
// x => (x * x)

U kunt lambda-expressies gebruiken in elke code waarvoor instanties van gemachtigde typen of expressiestructuren zijn vereist, bijvoorbeeld als argument voor de Task.Run(Action) methode om de code door te geven die op de achtergrond moet worden uitgevoerd. U kunt ook lambda-expressies gebruiken wanneer u LINQ schrijft in C#, zoals in het volgende voorbeeld wordt weergegeven:

int[] numbers = { 2, 3, 4, 5 };
var squaredNumbers = numbers.Select(x => x * x);
Console.WriteLine(string.Join(" ", squaredNumbers));
// Output:
// 4 9 16 25

Wanneer u syntaxis op basis van methoden gebruikt om de Enumerable.Select methode in de System.Linq.Enumerable klasse aan te roepen, bijvoorbeeld in LINQ naar objecten en LINQ naar XML, is de parameter een gemachtigde.System.Func<T,TResult> Wanneer u de Queryable.Select methode in de System.Linq.Queryable klasse aanroept, bijvoorbeeld in LINQ naar SQL, is het parametertype een type expressiestructuur Expression<Func<TSource,TResult>>. In beide gevallen kunt u dezelfde lambda-expressie gebruiken om de parameterwaarde op te geven. Hierdoor zien de twee Select aanroepen er ongeveer als volgt uit, hoewel het type objecten dat is gemaakt op basis van de lambdas anders is.

Expressie lambdas

Een lambda-expressie met een expressie aan de rechterkant van de => operator wordt een lambda voor expressies genoemd. Een expressie lambda retourneert het resultaat van de expressie en heeft de volgende basisvorm:

(input-parameters) => expression

De hoofdtekst van een expressie lambda kan bestaan uit een methodeaanroep. Als u echter expressiestructuren maakt die buiten de context van de .NET Common Language Runtime (CLR), zoals in SQL Server, worden geëvalueerd, moet u geen methodeaanroepen gebruiken in lambda-expressies. De methoden hebben geen betekenis buiten de context van .NET Common Language Runtime (CLR).

Instructie lambdas

Een instructie lambda lijkt op een expressie lambda, behalve dat de instructies tussen accolades staan:

(input-parameters) => { <sequence-of-statements> }

De hoofdtekst van een instructie lambda kan bestaan uit een willekeurig aantal instructies; In de praktijk zijn er echter meestal niet meer dan twee of drie.

Action<string> greet = name =>
{
    string greeting = $"Hello {name}!";
    Console.WriteLine(greeting);
};
greet("World");
// Output:
// Hello World!

U kunt geen instructie lambdas gebruiken om expressiestructuren te maken.

Invoerparameters van een lambda-expressie

U plaatst invoerparameters van een lambda-expressie tussen haakjes. Geef nul invoerparameters met lege haakjes op:

Action line = () => Console.WriteLine();

Als een lambda-expressie slechts één invoerparameter heeft, zijn haakjes optioneel:

Func<double, double> cube = x => x * x * x;

Twee of meer invoerparameters worden gescheiden door komma's:

Func<int, int, bool> testForEquality = (x, y) => x == y;

Soms kan de compiler de typen invoerparameters niet afleiden. U kunt de typen expliciet opgeven, zoals wordt weergegeven in het volgende voorbeeld:

Func<int, string, bool> isTooLong = (int x, string s) => s.Length > x;

Invoerparametertypen moeten allemaal expliciet of impliciet zijn; anders treedt er een CS0748-compilerfout op.

U kunt verwijderingen gebruiken om twee of meer invoerparameters op te geven van een lambda-expressie die niet worden gebruikt in de expressie:

Func<int, int, int> constant = (_, _) => 42;

Lambda-verwijderingsparameters kunnen handig zijn wanneer u een lambda-expressie gebruikt om een gebeurtenis-handler op te geven.

Notitie

Als voor compatibiliteit met eerdere versies slechts één invoerparameter wordt genoemd _, wordt binnen een lambda-expressie _ behandeld als de naam van die parameter.

Vanaf C# 12 kunt u standaardwaarden opgeven voor parameters in lambda-expressies. De syntaxis en de beperkingen voor standaardparameterwaarden zijn hetzelfde als voor methoden en lokale functies. In het volgende voorbeeld wordt een lambda-expressie met een standaardparameter declareren, waarna deze eenmaal wordt aanroepen met behulp van de standaardwaarde en eenmaal met twee expliciete parameters:

var IncrementBy = (int source, int increment = 1) => source + increment;

Console.WriteLine(IncrementBy(5)); // 6
Console.WriteLine(IncrementBy(5, 2)); // 7

U kunt ook lambda-expressies met params matrices als parameters declareren:

var sum = (params int[] values) =>
{
    int sum = 0;
    foreach (var value in values) 
        sum += value;
    
    return sum;
};

var empty = sum();
Console.WriteLine(empty); // 0

var sequence = new[] { 1, 2, 3, 4, 5 };
var total = sum(sequence);
Console.WriteLine(total); // 15

Als onderdeel van deze updates, wanneer een methodegroep met een standaardparameter wordt toegewezen aan een lambda-expressie, heeft die lambda-expressie ook dezelfde standaardparameter. Een methodegroep met een params matrixparameter kan ook worden toegewezen aan een lambda-expressie.

Lambda-expressies met standaardparameters of params matrices als parameters hebben geen natuurlijke typen die overeenkomen met Func<> of Action<> typen. U kunt echter gedelegeerdentypen definiëren die standaardparameterwaarden bevatten:

delegate int IncrementByDelegate(int source, int increment = 1);
delegate int SumDelegate(params int[] values);

U kunt ook impliciet getypte variabelen met var declaraties gebruiken om het type gedelegeerde te definiëren. De compiler synthetiseert het juiste gemachtigde type.

Zie de functiespecificatie voor standaardparameters voor lambda-expressies voor meer informatie.

Asynchrone lambdas

U kunt eenvoudig lambda-expressies en instructies maken die asynchrone verwerking bevatten met behulp van de asynchroneen wachtende trefwoorden. Het volgende Voorbeeld van Windows Forms bevat bijvoorbeeld een gebeurtenishandler die een asynchrone methode aanroept en wacht. ExampleMethodAsync

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        button1.Click += button1_Click;
    }

    private async void button1_Click(object sender, EventArgs e)
    {
        await ExampleMethodAsync();
        textBox1.Text += "\r\nControl returned to Click event handler.\n";
    }

    private async Task ExampleMethodAsync()
    {
        // The following line simulates a task-returning asynchronous process.
        await Task.Delay(1000);
    }
}

U kunt dezelfde gebeurtenis-handler toevoegen met behulp van een asynchrone lambda. Als u deze handler wilt toevoegen, voegt u een async wijzigingsfunctie toe vóór de lambda-parameterlijst, zoals in het volgende voorbeeld wordt weergegeven:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        button1.Click += async (sender, e) =>
        {
            await ExampleMethodAsync();
            textBox1.Text += "\r\nControl returned to Click event handler.\n";
        };
    }

    private async Task ExampleMethodAsync()
    {
        // The following line simulates a task-returning asynchronous process.
        await Task.Delay(1000);
    }
}

Zie Asynchrone programmering met asynchroon programmeren en wachten voor meer informatie over het maken en gebruiken van asynchrone methoden.

Lambda-expressies en tuples

De C#-taal biedt ingebouwde ondersteuning voor tuples. U kunt een tupel opgeven als argument voor een lambda-expressie en uw lambda-expressie kan ook een tuple retourneren. In sommige gevallen gebruikt de C#-compiler typedeductie om de typen tuple-onderdelen te bepalen.

U definieert een tuple door een door komma's gescheiden lijst met de bijbehorende onderdelen tussen haakjes te sluiten. In het volgende voorbeeld wordt tuple met drie onderdelen gebruikt om een reeks getallen door te geven aan een lambda-expressie, die elke waarde verdubbelt en een tuple retourneert met drie onderdelen die het resultaat van de vermenigvuldiging bevatten.

Func<(int, int, int), (int, int, int)> doubleThem = ns => (2 * ns.Item1, 2 * ns.Item2, 2 * ns.Item3);
var numbers = (2, 3, 4);
var doubledNumbers = doubleThem(numbers);
Console.WriteLine($"The set {numbers} doubled: {doubledNumbers}");
// Output:
// The set (2, 3, 4) doubled: (4, 6, 8)

Normaal gesproken hebben de velden van een tuple de naam Item1, Item2enzovoort. U kunt echter een tuple met benoemde onderdelen definiëren, zoals in het volgende voorbeeld.

Func<(int n1, int n2, int n3), (int, int, int)> doubleThem = ns => (2 * ns.n1, 2 * ns.n2, 2 * ns.n3);
var numbers = (2, 3, 4);
var doubledNumbers = doubleThem(numbers);
Console.WriteLine($"The set {numbers} doubled: {doubledNumbers}");

Zie Tuple-typen voor meer informatie over C#-tuples.

Lambdas met de standaardqueryoperators

LINQ naar objecten heeft onder andere een invoerparameter waarvan het type een van de Func<TResult> algemene gemachtigden is. Deze gemachtigden gebruiken typeparameters om het aantal en het type invoerparameters te definiëren en het retourtype van de gemachtigde. Func gedelegeerden zijn handig voor het inkapselen van door de gebruiker gedefinieerde expressies die worden toegepast op elk element in een set brongegevens. Denk bijvoorbeeld aan het Func<T,TResult> type gemachtigde:

public delegate TResult Func<in T, out TResult>(T arg)

De gemachtigde kan worden geïnstantieerd als een Func<int, bool> exemplaar waarin int een invoerparameter is en bool de retourwaarde is. De retourwaarde wordt altijd opgegeven in de laatste typeparameter. Definieert bijvoorbeeld Func<int, string, bool> een gemachtigde met twee invoerparameters int en string, en een retourtype bool. De volgende Func gemachtigde retourneert, wanneer deze wordt aangeroepen, booleaanse waarde die aangeeft of de invoerparameter gelijk is aan vijf:

Func<int, bool> equalsFive = x => x == 5;
bool result = equalsFive(4);
Console.WriteLine(result);   // False

U kunt ook een lambda-expressie opgeven wanneer het argumenttype een Expression<TDelegate>is, bijvoorbeeld in de standaardqueryoperators die zijn gedefinieerd in het Queryable type. Wanneer u een Expression<TDelegate> argument opgeeft, wordt de lambda gecompileerd naar een expressiestructuur.

In het volgende voorbeeld wordt de Count standaardqueryoperator gebruikt:

int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int oddNumbers = numbers.Count(n => n % 2 == 1);
Console.WriteLine($"There are {oddNumbers} odd numbers in {string.Join(" ", numbers)}");

De compiler kan het type invoerparameter afleiden of u kunt deze ook expliciet opgeven. Deze specifieke lambda-expressie telt die gehele getallen (n) die bij gedeeld door twee een rest van 1 hebben.

In het volgende voorbeeld wordt een reeks gegenereerd die alle elementen in de numbers matrix bevat die voorafgaan aan de 9, omdat dat het eerste getal is in de reeks die niet aan de voorwaarde voldoet:

int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var firstNumbersLessThanSix = numbers.TakeWhile(n => n < 6);
Console.WriteLine(string.Join(" ", firstNumbersLessThanSix));
// Output:
// 5 4 1 3

In het volgende voorbeeld worden meerdere invoerparameters opgegeven door deze tussen haakjes te zetten. De methode retourneert alle elementen in de numbers matrix totdat een getal wordt gevonden waarvan de waarde kleiner is dan de rangtelpositie in de matrix:

int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var firstSmallNumbers = numbers.TakeWhile((n, index) => n >= index);
Console.WriteLine(string.Join(" ", firstSmallNumbers));
// Output:
// 5 4

U gebruikt geen lambda-expressies rechtstreeks in query-expressies, maar u kunt ze gebruiken in methode-aanroepen binnen query-expressies, zoals in het volgende voorbeeld wordt weergegeven:

var numberSets = new List<int[]>
{
    new[] { 1, 2, 3, 4, 5 },
    new[] { 0, 0, 0 },
    new[] { 9, 8 },
    new[] { 1, 0, 1, 0, 1, 0, 1, 0 }
};

var setsWithManyPositives = 
    from numberSet in numberSets
    where numberSet.Count(n => n > 0) > 3
    select numberSet;

foreach (var numberSet in setsWithManyPositives)
{
    Console.WriteLine(string.Join(" ", numberSet));
}
// Output:
// 1 2 3 4 5
// 1 0 1 0 1 0 1 0

Typ deductie in lambda-expressies

Bij het schrijven van lambdas hoeft u vaak geen type op te geven voor de invoerparameters, omdat de compiler het type kan afleiden op basis van de lambda-hoofdtekst, de parametertypen en andere factoren zoals beschreven in de C#-taalspecificatie. Voor de meeste standaardqueryoperators is de eerste invoer het type elementen in de bronreeks. Als u een query uitvoert op een IEnumerable<Customer>, wordt de invoervariabele afgeleid van een Customer object, wat betekent dat u toegang hebt tot de methoden en eigenschappen:

customers.Where(c => c.City == "London");

De algemene regels voor typedeductie voor lambdas zijn als volgt:

  • De lambda moet hetzelfde aantal parameters bevatten als het type gemachtigde.
  • Elke invoerparameter in de lambda moet impliciet worden omgezet in de bijbehorende gedelegeerde parameter.
  • De retourwaarde van de lambda (indien aanwezig) moet impliciet worden omgezet in het retourtype van de gemachtigde.

Natuurlijk type lambda-expressie

Een lambda-expressie op zichzelf heeft geen type omdat het algemene typesysteem geen intrinsiek concept van 'lambda-expressie' heeft. Het is echter soms handig om informeel te spreken van het 'type' van een lambda-expressie. Dat informele 'type' verwijst naar het gemachtigde type of Expression type waarnaar de lambda-expressie wordt geconverteerd.

Vanaf C# 10 kan een lambda-expressie een natuurlijk type hebben. In plaats van af te dwingen dat u een gemachtigdentype declareert, zoals Func<...> of Action<...> voor een lambda-expressie, kan de compiler het type gemachtigde afleiden uit de lambda-expressie. Denk bijvoorbeeld aan de volgende declaratie:

var parse = (string s) => int.Parse(s);

De compiler kan afleiden parse dat het een Func<string, int>. De compiler kiest een beschikbare Func of Action gemachtigde als er een geschikte bestaat. Anders wordt een type gemachtigde gesynthetiseert. Het type gemachtigde wordt bijvoorbeeld gesynthetiseerd als de lambda-expressie parameters bevat ref . Wanneer een lambda-expressie een natuurlijk type heeft, kan deze worden toegewezen aan een minder expliciet type, zoalsSystem.Object:System.Delegate

object parse = (string s) => int.Parse(s);   // Func<string, int>
Delegate parse = (string s) => int.Parse(s); // Func<string, int>

Methodegroepen (dat wil zeggen, methodenamen zonder parameterlijsten) met precies één overbelasting hebben een natuurlijk type:

var read = Console.Read; // Just one overload; Func<int> inferred
var write = Console.Write; // ERROR: Multiple overloads, can't choose

Als u een lambda-expressie toewijst aan System.Linq.Expressions.LambdaExpression, of System.Linq.Expressions.Expression, en de lambda een natuurlijk gemachtigdetype heeft, heeft de expressie een natuurlijk type System.Linq.Expressions.Expression<TDelegate>, waarbij het natuurlijke gemachtigde type wordt gebruikt als het argument voor de typeparameter:

LambdaExpression parseExpr = (string s) => int.Parse(s); // Expression<Func<string, int>>
Expression parseExpr = (string s) => int.Parse(s);       // Expression<Func<string, int>>

Niet alle lambda-expressies hebben een natuurlijk type. Houd rekening met de volgende declaratie:

var parse = s => int.Parse(s); // ERROR: Not enough type info in the lambda

De compiler kan geen parametertype afleiden voor s. Wanneer de compiler een natuurlijk type niet kan afleiden, moet u het type declareren:

Func<string, int> parse = s => int.Parse(s);

Expliciet retourtype

Normaal gesproken is het retourtype van een lambda-expressie duidelijk en afgeleid. Voor sommige expressies die niet werken:

var choose = (bool b) => b ? 1 : "two"; // ERROR: Can't infer return type

Vanaf C# 10 kunt u het retourtype van een lambda-expressie opgeven vóór de invoerparameters. Wanneer u een expliciet retourtype opgeeft, moet u de invoerparameters haakjes maken:

var choose = object (bool b) => b ? 1 : "two"; // Func<bool, object>

Kenmerken

Vanaf C# 10 kunt u kenmerken toevoegen aan een lambda-expressie en de bijbehorende parameters. In het volgende voorbeeld ziet u hoe u kenmerken toevoegt aan een lambda-expressie:

Func<string?, int?> parse = [ProvidesNullCheck] (s) => (s is not null) ? int.Parse(s) : null;

U kunt ook kenmerken toevoegen aan de invoerparameters of retourwaarde, zoals in het volgende voorbeeld wordt weergegeven:

var concat = ([DisallowNull] string a, [DisallowNull] string b) => a + b;
var inc = [return: NotNullIfNotNull(nameof(s))] (int? s) => s.HasValue ? s++ : null;

Zoals in de voorgaande voorbeelden wordt weergegeven, moet u de invoerparameters haakjes maken wanneer u kenmerken toevoegt aan een lambda-expressie of de bijbehorende parameters.

Belangrijk

Lambda-expressies worden aangeroepen via het onderliggende gedelegeerdentype. Dat is anders dan methoden en lokale functies. De methode van Invoke de gedelegeerde controleert geen kenmerken in de lambda-expressie. Kenmerken hebben geen effect wanneer de lambda-expressie wordt aangeroepen. Kenmerken van lambda-expressies zijn handig voor codeanalyse en kunnen worden gedetecteerd via weerspiegeling. Een gevolg van deze beslissing is dat het System.Diagnostics.ConditionalAttribute niet kan worden toegepast op een lambda-expressie.

Capture of outer variables and variable scope in lambda expressions

Lambdas kan verwijzen naar buitenste variabelen. Deze buitenste variabelen zijn de variabelen die binnen het bereik vallen in de methode waarmee de lambda-expressie wordt gedefinieerd, of in het bereik van het type dat de lambda-expressie bevat. Variabelen die op deze manier worden vastgelegd, worden opgeslagen voor gebruik in de lambda-expressie, zelfs als de variabelen anders buiten het bereik vallen en afval worden verzameld. Een buitenste variabele moet zeker worden toegewezen voordat deze kan worden gebruikt in een lambda-expressie. In het volgende voorbeeld ziet u de volgende regels:

public static class VariableScopeWithLambdas
{
    public class VariableCaptureGame
    {
        internal Action<int>? updateCapturedLocalVariable;
        internal Func<int, bool>? isEqualToCapturedLocalVariable;

        public void Run(int input)
        {
            int j = 0;

            updateCapturedLocalVariable = x =>
            {
                j = x;
                bool result = j > input;
                Console.WriteLine($"{j} is greater than {input}: {result}");
            };

            isEqualToCapturedLocalVariable = x => x == j;

            Console.WriteLine($"Local variable before lambda invocation: {j}");
            updateCapturedLocalVariable(10);
            Console.WriteLine($"Local variable after lambda invocation: {j}");
        }
    }

    public static void Main()
    {
        var game = new VariableCaptureGame();

        int gameInput = 5;
        game.Run(gameInput);

        int jTry = 10;
        bool result = game.isEqualToCapturedLocalVariable!(jTry);
        Console.WriteLine($"Captured local variable is equal to {jTry}: {result}");

        int anotherJ = 3;
        game.updateCapturedLocalVariable!(anotherJ);

        bool equalToAnother = game.isEqualToCapturedLocalVariable(anotherJ);
        Console.WriteLine($"Another lambda observes a new value of captured variable: {equalToAnother}");
    }
    // Output:
    // Local variable before lambda invocation: 0
    // 10 is greater than 5: True
    // Local variable after lambda invocation: 10
    // Captured local variable is equal to 10: True
    // 3 is greater than 5: False
    // Another lambda observes a new value of captured variable: True
}

De volgende regels zijn van toepassing op het variabele bereik in lambda-expressies:

  • Een variabele die wordt vastgelegd, wordt pas door garbagecollection verzameld als de gemachtigde waarnaar wordt verwezen, in aanmerking komt voor garbagecollection.
  • Variabelen die in een lambda-expressie worden geïntroduceerd, zijn niet zichtbaar in de insluitmethode.
  • Een lambda-expressie kan een in-, verw- of uitparameter niet rechtstreeks vastleggen vanuit de insluitmethode.
  • Een retourinstructie in een lambda-expressie zorgt er niet voor dat de omsluitmethode wordt geretourneerd.
  • Een lambda-expressie kan geen goto-, onderbrekings- of continue-instructie bevatten als het doel van die jump-instructie zich buiten het lambda-expressieblok bevindt. Het is ook een fout om een jump-instructie buiten het lambda-expressieblok te hebben als het doel zich in het blok bevindt.

U kunt de static wijzigingsfunctie toepassen op een lambda-expressie om onbedoelde opname van lokale variabelen of instantiestatus door de lambda te voorkomen:

Func<double, double> square = static x => x * x;

Een statische lambda kan geen lokale variabelen of instantiestatus vastleggen vanuit de bereiken die zich insluiten, maar kan verwijzen naar statische leden en constante definities.

C#-taalspecificatie

Zie de sectie Anonieme functie-expressies van de C#-taalspecificatie voor meer informatie.

Zie de volgende opmerkingen over functievoorstel voor meer informatie over deze functies:

Zie ook