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:

  • Expression lambda som har ett uttryck som brödtext:

    (input-parameters) => expression
    
  • Statement lambda som har ett instruktionsblock som 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 delegattyp. 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 Action ombudstyperna. Annars kan den konverteras till någon av de Func ombudstyperna. 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 med en parameter som returnerar ett värde kan konverteras till en Func<T,TResult> delegering. I följande exempel tilldelas lambda-uttrycket x => x * x, som anger en parameter med namnet x och returnerar värdet för x kvadrat, till en variabel av en ombudstyp:

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

Lambdauttryck kan också konverteras till -uttrycksträd av typen, vilket visas i följande exempel:

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 metoden Task.Run(Action) 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 metoden Enumerable.Select i klassen System.Linq.Enumerable, till exempel i LINQ till Objekt och LINQ till XML, är parametern en ombudstyp System.Func<T,TResult>. När du anropar metoden Queryable.Select i klassen System.Linq.Queryable, 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.

Lambda-uttryck

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

(input-parameters) => expression

Kroppen i ett lambda-uttryck 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).

Uttryck lambdas

En lambda-sats liknar ett lambda-uttryck, förutom att dess satser 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 uttryckslambdas 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 tar bort 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 ange en händelsehanterare.

Not

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

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

Du kan också 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 genom att använda 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 async och await.

Lambda-uttryck och tupplar

Språket C# har 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 tupel genom att omsluta en kommaavgränsad lista över dess komponenter med parenteser. I följande exempel används en tuppel med tre komponenter för att skicka en talföljd till ett lambda-uttryck, som fördubblar varje värde och returnerar en tuppel med tre komponenter som innehåller resultaten 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 fälten i en tuppeln Item1, Item2och så vidare. Du kan dock definiera en tupl med namngivna komponenter, som följande exempel gör.

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 och deras typer finns i Tupplar typer.

Lambdas med standardfrågeoperatorerna

LINQ Till Objekt, bland andra implementeringar, har en indataparameter vars typ är en av familjen Func<TResult> med generiska delegeringar. Dessa ombud använder typparametrar för att definiera antalet och typen av indataparametrar och ombudets returtyp. Func Delegater är användbara för att kapsla in användardefinierade uttryck som tillämpas på varje element i en datamängd av källdata. Tänk till exempel på Func<T,TResult> ombudstyp:

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. Till exempel definierar Func<int, string, bool> ett ombud med två indataparametrar, int och stringoch en returtyp av bool. Följande Func ombud 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ågaoperator:

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 den numbers matris 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 den numbers matrisen 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 frågar en IEnumerable<Customer>antas indatavariabeln vara 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:

  • En lambda måste innehålla samma antal parametrar som delegationstypen.
  • 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 delegatens 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.

Ett lambda-uttryck kan 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ärleda parse att vara en Func<string, int>. Kompilatorn väljer en tillgänglig Func- eller Action-delegering, om en lämplig sådan finns. 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.Expression, och 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

Du kan 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

Du kan 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.

Viktig

Lambda-uttryck anropas via den underliggande delegattypen. Det skiljer sig från metoder och lokala funktioner. Ombudets Invoke-metod kontrollerar inte attribut i 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 System.Diagnostics.ConditionalAttribute inte kan tillämpas på ett lambda-uttryck.

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

Lambdas kan hänvisa till yttre variabler. Dessa yttre variabler är variablerna som är i räckvidden av metoden som definierar lambda-uttrycket, eller är i räckvidden av typen 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 är fångad kommer inte att skräpsamlas förrän delegaten som refererar till den är berättigad till garbage collection.
  • Variabler som introduceras i ett lambda-uttryck visas inte i omslutningsmetoden.
  • Ett lambda-uttryck kan inte direkt fånga en i, refeller ut-parametern från den omslutande metoden.
  • En return-sats i ett lambda-uttryck gör inte att den omslutande metoden returnerar.
  • Ett lambda-uttryck kan inte innehålla en goto, breakeller continue-instruktion om målet för den hopp-instruktionen ligger utanför lambda-uttrycket. 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-modifikatorn på ett lambda-uttryck för att förhindra oavsiktlig infångning av lokala variabler eller instanstillstånd av lambdan.

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

En statisk lambda kan inte fånga lokala variabler eller instanser från omslutande 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