Lambda-uttryck och anonyma funktioner

Du använder ett lambda-uttryck för att skapa en anonym funktion. Använd lambda-deklarationsoperatorn => för att separera lambda-parameterlistan från dess brödtext. Ett lambda-uttryck kan vara av någon av följande två former:

  • Uttryck lambda som har ett uttryck som sin brödtext:

    (input-parameters) => expression
    
  • Instruktion lambda som har ett instruktionsblock som sin brödtext:

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

Om du vill skapa ett lambda-uttryck anger du indataparametrar (om några) till vänster om lambdaoperatorn och ett uttryck eller ett instruktionsblock på andra sidan.

Alla lambda-uttryck kan konverteras till en ombudstyp . Den ombudstyp som ett lambda-uttryck kan konverteras till definieras av typerna av dess parametrar och returvärdet. Om ett lambda-uttryck inte returnerar ett värde kan det konverteras till någon av de delegerade typerna Action . Annars kan det konverteras till någon av de delegerade typerna Func . Till exempel kan ett lambda-uttryck som har två parametrar och returnerar inget värde konverteras till ett Action<T1,T2> ombud. Ett lambda-uttryck som har en parameter och returnerar ett värde kan konverteras till ett Func<T,TResult> ombud. I följande exempel tilldelas lambda-uttrycket x => x * x, som anger en parameter som heter x och returnerar värdet x för kvadrat, till en variabel av en ombudstyp:

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

Lambdas för uttryck kan också konverteras till uttrycksträdstyperna, som följande exempel visar:

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

Du kan använda lambda-uttryck i valfri kod som kräver instanser av ombudstyper eller uttrycksträd, till exempel som ett argument till Task.Run(Action) metoden för att skicka koden som ska köras i bakgrunden. Du kan också använda lambda-uttryck när du skriver LINQ i C#, som följande exempel visar:

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

När du använder metodbaserad syntax för att anropa Enumerable.Select metoden i System.Linq.Enumerable klassen, till exempel i LINQ till Objekt och LINQ till XML, är parametern en ombudstyp System.Func<T,TResult>. När du anropar Queryable.Select metoden i System.Linq.Queryable klassen, till exempel i LINQ till SQL, är parametertypen en uttrycksträdstyp Expression<Func<TSource,TResult>>. I båda fallen kan du använda samma lambda-uttryck för att ange parametervärdet. Det gör att de två Select anropen ser likadana ut, även om den typ av objekt som skapats från lambdas i själva verket är annorlunda.

Uttryck lambdas

Ett lambda-uttryck med ett uttryck till höger om operatorn => kallas för ett uttryck lambda. Ett uttryck lambda returnerar resultatet av uttrycket och har följande grundläggande form:

(input-parameters) => expression

Brödtexten i ett uttryck lambda kan bestå av ett metodanrop. Men om du skapar uttrycksträd som utvärderas utanför kontexten för .NET Common Language Runtime (CLR), till exempel i SQL Server, bör du inte använda metodanrop i lambda-uttryck. Metoderna har ingen betydelse utanför kontexten för .NET Common Language Runtime (CLR).

Instruktion lambdas

En instruktion lambda liknar ett uttryck lambda förutom att dess uttalanden omges av klammerparenteser:

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

Brödtexten i en instruktion lambda kan bestå av valfritt antal instruktioner; I praktiken finns det dock vanligtvis inte fler än två eller tre.

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

Du kan inte använda uttryckslamdas för att skapa uttrycksträd.

Indataparametrar för ett lambda-uttryck

Du omger indataparametrar för ett lambda-uttryck inom parenteser. Ange noll indataparametrar med tomma parenteser:

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

Om ett lambda-uttryck bara har en indataparameter är parenteser valfria:

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

Två eller flera indataparametrar avgränsas med kommatecken:

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

Ibland kan kompilatorn inte härleda typerna av indataparametrar. Du kan ange typerna explicit enligt följande exempel:

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

Indataparametertyper måste vara alla explicita eller alla implicita. Annars uppstår ett CS0748-kompilatorfel .

Du kan använda ignorera för att ange två eller flera indataparametrar för ett lambda-uttryck som inte används i uttrycket:

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

Parametrar för lambda-ignorerande kan vara användbara när du använder ett lambda-uttryck för att tillhandahålla en händelsehanterare.

Kommentar

För bakåtkompatibilitet, om endast en enskild indataparameter heter _, behandlas i ett lambda-uttryck _ som namnet på parametern.

Från och med C# 12 kan du ange standardvärden för parametrar för lambda-uttryck. Syntaxen och begränsningarna för standardparametervärden är desamma som för metoder och lokala funktioner. I följande exempel deklareras ett lambda-uttryck med en standardparameter och anropar det sedan en gång med standardvärdet och en gång med två explicita parametrar:

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

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

Du kan också deklarera lambda-uttryck med params matriser som parametrar:

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

När en metodgrupp som har en standardparameter tilldelas till ett lambda-uttryck som en del av dessa uppdateringar har lambda-uttrycket också samma standardparameter. En metodgrupp med en params matrisparameter kan också tilldelas till ett lambda-uttryck.

Lambda-uttryck med standardparametrar eller params matriser som parametrar har inte naturliga typer som motsvarar Func<> eller Action<> typer. Du kan dock definiera ombudstyper som innehåller standardparametervärden:

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

Eller så kan du använda implicit inskrivna variabler med var deklarationer för att definiera ombudstypen. Kompilatorn syntetiserar rätt ombudstyp.

Mer information finns i funktionsspecifikationen för standardparametrar för lambda-uttryck.

Async lambdas

Du kan enkelt skapa lambda-uttryck och -instruktioner som innehåller asynkron bearbetning med hjälp av nyckelorden async och await . Följande Windows Forms-exempel innehåller till exempel en händelsehanterare som anropar och väntar på en asynkron metod, 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);
    }
}

Du kan lägga till samma händelsehanterare med hjälp av en asynkron lambda. Lägg till den här hanteraren genom att lägga till en async modifierare före lambda-parameterlistan, som följande exempel visar:

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

Mer information om hur du skapar och använder asynkrona metoder finns i Asynkron programmering med asynkron programmering och väntar.

Lambda-uttryck och tupplar

C#-språket ger inbyggt stöd för tupplar. Du kan ange en tuppeln som ett argument till ett lambda-uttryck, och ditt lambda-uttryck kan också returnera en tuppeln. I vissa fall använder C#-kompilatorn typinferens för att fastställa typerna av tuppelns komponenter.

Du definierar en tupplar genom att omsluta en kommaavgränsad lista över dess komponenter inom parenteser. I följande exempel används tuppeln med tre komponenter för att skicka en sekvens med tal till ett lambda-uttryck, vilket fördubblar varje värde och returnerar en tupplar med tre komponenter som innehåller resultatet av multiplikationerna.

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)

Normalt heter Item1fälten i en tuppl , Item2och så vidare. Du kan dock definiera en tuppeln med namngivna komponenter, som i följande exempel.

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}");

Mer information om C#-tupplar finns i Tupplar.

Lambdas med standardfrågeoperatorerna

LINQ till objekt, bland andra implementeringar, har en indataparameter vars typ är en av de allmänna ombudens Func<TResult> familj. Dessa ombud använder typparametrar för att definiera antalet och typen av indataparametrar och ombudets returtyp. Func ombud är användbara för att kapsla in användardefinierade uttryck som tillämpas på varje element i en uppsättning källdata. Tänk till exempel på ombudstypen Func<T,TResult> :

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

Ombudet kan instansieras som en Func<int, bool> instans där int är en indataparameter och bool är returvärdet. Returvärdet anges alltid i parametern för den sista typen. Definierar Func<int, string, bool> till exempel ett ombud med två indataparametrar och intstring, och en returtyp av bool. Följande Func ombud, när det anropas, returnerar booleskt värde som anger om indataparametern är lika med fem:

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

Du kan också ange ett lambda-uttryck när argumenttypen är en Expression<TDelegate>, till exempel i standardfrågeoperatorerna som definieras i Queryable typen . När du anger ett Expression<TDelegate> argument kompileras lambda till ett uttrycksträd.

I följande exempel används Count standardfrågeoperatorn:

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)}");

Kompilatorn kan härleda typen av indataparameter, eller så kan du också ange den explicit. Detta specifika lambda-uttryck räknar de heltal (n) som när de delas med två har resten av 1.

I följande exempel skapas en sekvens som innehåller alla element i matrisen numbers som föregår 9, eftersom det är det första talet i sekvensen som inte uppfyller villkoret:

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

I följande exempel anges flera indataparametrar genom att de omges av parenteser. Metoden returnerar alla element i matrisen numbers tills den hittar ett tal vars värde är mindre än dess ordningstal i matrisen:

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

Du använder inte lambda-uttryck direkt i frågeuttryck, men du kan använda dem i metodanrop i frågeuttryck, som följande exempel visar:

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

Skriv slutsatsdragning i lambda-uttryck

När du skriver lambdas behöver du ofta inte ange någon typ för indataparametrarna eftersom kompilatorn kan härleda typen baserat på lambda-brödtexten, parametertyperna och andra faktorer enligt beskrivningen i C#-språkspecifikationen. För de flesta vanliga frågeoperatorer är den första indatatypen för elementen i källsekvensen. Om du kör frågor mot en IEnumerable<Customer>, härleds indatavariabeln till ett Customer objekt, vilket innebär att du har åtkomst till dess metoder och egenskaper:

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

De allmänna reglerna för typinferens för lambdas är följande:

  • Lambda måste innehålla samma antal parametrar som ombudstypen.
  • Varje indataparameter i lambda måste implicit konverteras till motsvarande delegatparameter.
  • Returvärdet för lambda (om det finns något) måste implicit konverteras till ombudets returtyp.

Naturlig typ av ett lambda-uttryck

Ett lambda-uttryck i sig har ingen typ eftersom det gemensamma typsystemet inte har något inbyggt begrepp om "lambda-uttryck". Det är dock ibland praktiskt att tala informellt om "typen" av ett lambda-uttryck. Den informella "typen" refererar till den delegattyp eller Expression typ som lambda-uttrycket konverteras till.

Från och med C# 10 kan ett lambda-uttryck ha en naturlig typ. I stället för att tvinga dig att deklarera en delegattyp, till exempel Func<...> eller Action<...> för ett lambda-uttryck, kan kompilatorn härleda delegattypen från lambda-uttrycket. Tänk till exempel på följande deklaration:

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

Kompilatorn kan härledas parse till att vara en Func<string, int>. Kompilatorn väljer en tillgänglig Func eller Action delegerad, om det finns en lämplig sådan. Annars syntetiserar den en ombudstyp. Till exempel syntetiseras ombudstypen om lambda-uttrycket har ref parametrar. När ett lambda-uttryck har en naturlig typ kan det tilldelas till en mindre explicit typ, till exempel System.Object eller System.Delegate:

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

Metodgrupper (alltså metodnamn utan parameterlistor) med exakt en överlagring har en naturlig typ:

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

Om du tilldelar ett lambda-uttryck till System.Linq.Expressions.LambdaExpression, eller System.Linq.Expressions.Expressionoch lambda har en naturlig delegattyp, har uttrycket en naturlig typ av System.Linq.Expressions.Expression<TDelegate>, med den naturliga delegattypen som används som argument för typparametern:

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

Alla lambda-uttryck har inte en naturlig typ. Överväg följande deklaration:

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

Kompilatorn kan inte härleda en parametertyp för s. När kompilatorn inte kan härleda en naturlig typ måste du deklarera typen:

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

Explicit returtyp

Normalt är returtypen för ett lambda-uttryck uppenbar och härledd. För vissa uttryck som inte fungerar:

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

Från och med C# 10 kan du ange returtypen för ett lambda-uttryck före indataparametrarna. När du anger en explicit returtyp måste du parentesera indataparametrarna:

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

Attribut

Från och med C# 10 kan du lägga till attribut till ett lambda-uttryck och dess parametrar. I följande exempel visas hur du lägger till attribut i ett lambda-uttryck:

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

Du kan också lägga till attribut till indataparametrarna eller returvärdet, som följande exempel visar:

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

Som föregående exempel visar måste du parentesera indataparametrarna när du lägger till attribut till ett lambda-uttryck eller dess parametrar.

Viktigt!

Lambda-uttryck anropas via den underliggande delegattypen. Det skiljer sig från metoder och lokala funktioner. Ombudets Invoke metod kontrollerar inte attribut för lambda-uttrycket. Attribut har ingen effekt när lambda-uttrycket anropas. Attribut för lambda-uttryck är användbara för kodanalys och kan identifieras via reflektion. En konsekvens av detta beslut är att det System.Diagnostics.ConditionalAttribute inte kan tillämpas på ett lambda-uttryck.

Avbildning av yttre variabler och variabelomfång i lambda-uttryck

Lambdas kan referera till yttre variabler. Dessa yttre variabler är variablerna som finns i omfånget i metoden som definierar lambda-uttrycket, eller i omfånget i den typ som innehåller lambda-uttrycket. Variabler som samlas in på det här sättet lagras för användning i lambda-uttrycket även om variablerna annars skulle gå utanför omfånget och vara skräpinsamling. En yttre variabel måste definitivt tilldelas innan den kan användas i ett lambda-uttryck. Följande exempel visar dessa regler:

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
}

Följande regler gäller för variabelomfång i lambda-uttryck:

  • En variabel som samlas in samlas inte in av skräp förrän ombudet som refererar till den blir berättigad till skräpinsamling.
  • Variabler som introduceras i ett lambda-uttryck visas inte i omslutningsmetoden.
  • Ett lambda-uttryck kan inte direkt avbilda en in-, referens- eller ut-parameter från omslutningsmetoden.
  • En retursats i ett lambda-uttryck gör inte att omslutningsmetoden returneras.
  • Ett lambda-uttryck får inte innehålla en goto-, break- eller continue-instruktion om målet för den jump-instruktionen ligger utanför lambda-uttrycksblocket. Det är också ett fel att ha en jump-instruktion utanför lambda-uttrycksblocket om målet finns i blocket.

Du kan använda static modifieraren för ett lambda-uttryck för att förhindra oavsiktlig insamling av lokala variabler eller instanstillstånd av lambda:

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

En statisk lambda kan inte samla in lokala variabler eller instanstillstånd från omfång, men kan referera till statiska medlemmar och konstanta definitioner.

Språkspecifikation för C#

Mer information finns i avsnittet Anonyma funktionsuttryck i C#-språkspecifikationen.

Mer information om dessa funktioner finns i följande kommentarer om funktionsförslag:

Se även