Dela via


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 . Typerna av dess parametrar och returvärde definierar den ombudstyp som ett lambda-uttryck kan konverteras till. 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 med namnet x och returnerar värdet x för kvadrat, till en variabel av en delegattyp:

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 använder lambda-uttryck i valfri kod som kräver instanser av ombudstyper eller uttrycksträd. Ett exempel är argumentet 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;

Lambda-parametrar 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 eller samlingar som parametrar:

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

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 samlingsparameter kan också tilldelas till ett lambda-uttryck.

Lambda-uttryck med standardparametrar eller params samlingar 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);
delegate int SumCollectionDelegate(params IEnumerable<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 om standardparametrar för lambda-uttryck 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. När följande Func ombud anropas returneras 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