Lezen in het Engels

Delen via


Lambda-expressies en anonieme functies

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

  • Expression lambda met een expressie als hoofdtekst:

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

    C#
    (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 delegate type. De typen parameters en retourwaarde definiëren het gemachtigde type waarnaar een lambda-expressie kan worden geconverteerd. 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 die twee parameters heeft en geen waarde retourneert, kan worden omgezet naar een Action<T1,T2>-delegate. Een lambda-expressie met één parameter die een waarde retourneert, kan worden geconverteerd naar een Func<T,TResult> delegate. In het volgende voorbeeld wordt de lambda-expressie x => x * x, waarmee een parameter met de naam x wordt opgegeven en het kwadraat van de waarde van x wordt geretourneerd, toegewezen aan een variabele van een delegate-type.

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

Lambdas voor expressies kunnen ook worden geconverteerd naar de expressiestructuur typen, zoals in het volgende voorbeeld wordt weergegeven:

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

U gebruikt lambda-expressies in elke code waarvoor instanties van gedelegeerde typen of expressiebomen zijn vereist. Een voorbeeld is het 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:

C#
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 aan te roepen in de klasse System.Linq.Enumerable, bijvoorbeeld in LINQ naar objecten en LINQ naar XML, is de parameter een gemachtigdentype System.Func<T,TResult>. Wanneer u de methode Queryable.Select aanroept in de System.Linq.Queryable-klasse, bijvoorbeeld in LINQ naar SQL, is het parametertype een expressiestructuurtype Expression<Func<TSource,TResult>>. In beide gevallen kunt u dezelfde lambda-expressie gebruiken om de parameterwaarde op te geven. Hierdoor zien de twee Select oproepen 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 expressie lambdagenoemd. Een expressie lambda retourneert het resultaat van de expressie en heeft de volgende basisvorm:

C#
(input-parameters) => expression

De hoofdtekst van een expressie lambda kan bestaan uit een methodeaanroep. Als u echter expressiestructuren maakt die buiten de context van .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:

C#
(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.

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

U kunt geen statement-lambdas gebruiken om expressiebomen te maken.

Invoerparameters van een lambda-expressie

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

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

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

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

Twee of meer invoerparameters worden gescheiden door komma's:

C#
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:

C#
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 verwijderen gebruiken om twee of meer invoerparameters op te geven van een lambda-expressie die niet in de expressie worden gebruikt:

C#
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:

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

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

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

C#
var sum = (params IEnumerable<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 verzamelingsparameter params kan ook worden toegewezen aan een lambda-expressie.

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

C#
delegate int IncrementByDelegate(int source, int increment = 1);
delegate int SumDelegate(params int[] values);
delegate int SumCollectionDelegate(params IEnumerable<int> values);

U kunt ook impliciet getypte variabelen gebruiken met var declaraties om het type gemachtigde te definiëren. De compiler maakt het juiste delegate-type aan.

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

Asynchrone lambdas

U kunt eenvoudig lambda-expressies en instructies maken die asynchrone verwerking integreren door gebruik te maken van de trefwoorden async en await. Het volgende voorbeeld van Windows Forms bevat een gebeurtenishandler die een asynchrone methode aanroept en erop wacht, ExampleMethodAsync.

C#
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 modifier toe vóór de lambda-parameterlijst, zoals in het volgende voorbeeld wordt weergegeven:

C#
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);
    }
}

Voor meer informatie over het maken en gebruiken van asynchrone methoden, zie Asynchrone programmering met async en await.

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.

C#
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 worden de velden van een tuple benoemd Item1, Item2enzovoort. U kunt echter een tuple met benoemde onderdelen definiëren, zoals in het volgende voorbeeld.

C#
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-typenvoor meer informatie over C#-tuples.

Lambdas met de standaard query-operators

LINQ to Objects heeft onder andere een invoerparameter waarvan het type een van de Func<TResult> familie van algemene gemachtigden is. Deze gemachtigden gebruiken typeparameters om het aantal en het type invoerparameters te definiëren en het retourtype van de gemachtigde. Func gemachtigden 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 type Func<T,TResult> gedelegeerde:

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

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

C#
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:

C#
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 in de reeks is dat niet aan de voorwaarde voldoet:

C#
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:

C#
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 lambda-expressies niet rechtstreeks in query-expressies, maar u kunt deze gebruiken in methode-aanroepen binnen query-expressies, zoals in het volgende voorbeeld wordt weergegeven:

C#
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

Type-inferentie 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:

C#
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 delegaattype.
  • Elke invoerparameter in de lambda moet impliciet worden omgezet in de bijbehorende gedelegeerde parameter.
  • De retourwaarde van de lambda (indien aanwezig) moet impliciet kunnen worden omgezet naar het retourtype van de delegate.

Natuurlijk type van een 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 delegettype of Expression-type waarnaar de lambda-expressie wordt geconverteerd.

Een lambda-expressie kan een natuurlijk typehebben. 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:

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

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

C#
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:

C#
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 gedelegeerdentype heeft, heeft de expressie een natuurlijk type System.Linq.Expressions.Expression<TDelegate>, waarbij het natuurlijke gedelegeerdentype wordt gebruikt als het argument voor de typeparameter:

C#
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:

C#
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:

C#
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:

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

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

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

Kenmerken

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

C#
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:

C#
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 Invoke methode van 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 de System.Diagnostics.ConditionalAttribute niet kan worden toegepast op een lambda-expressie.

Vangen van externe variabelen en variabelebereik in lambda-uitdrukkingen

Lambdas kan verwijzen naar buitenste variabelen. Deze buitenste variabelen zijn de variabelen die zich binnen het bereik bevinden in de methode waarmee de lambda-expressie wordt gedefinieerd of binnen 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:

C#
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 opgeslagen, wordt pas door de garbage-collector opgeruimd als de delegate waarnaar wordt verwezen, in aanmerking komt voor garbage-collection.
  • Variabelen die in een lambda-expressie worden geïntroduceerd, zijn niet zichtbaar in de insluitmethode.
  • Een lambda-expressie kan een niet rechtstreeks vastleggen in, refof parameter uit de omsluitmethode.
  • Een instructie in een lambda-expressie retourneert, zorgt er niet voor dat de omsluitmethode wordt geretourneerd.
  • Een lambda-expressie kan geen goto, breakof continue instructie bevatten als het doel van die spronginstructie buiten het lambda-expressieblok ligt. Het is ook een fout om een spronginstructie buiten het lambda-expressieblok te plaatsen wanneer het doel zich in het blok bevindt.

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

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

Een statische lambda kan geen lokale variabelen of status van een instantie vastleggen vanuit omsluitende bereiken, maar kan wel verwijzen naar statische leden en constante definities.

C#-taalspecificatie

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

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

Zie ook