Bagikan melalui


Ekspresi Lambda dan fungsi anonim

Anda menggunakan ekspresi lambda untuk membuat sebuah fungsi anonim. Gunakan operator deklarasi lambda=> untuk memisahkan daftar parameter lambda dari tubuhnya. Ekspresi lambda dapat berupa salah satu dari dua bentuk berikut:

  • Ekspresi lambda yang memiliki ekspresi sebagai isinya:

    (input-parameters) => expression
    
  • Pernyataan lambda yang memiliki blok pernyataan sebagai isinya:

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

Untuk membuat ekspresi lambda, Anda menentukan parameter masukan (jika ada) di sisi kiri operator lambda dan ekspresi atau blok pernyataan di sisi lain.

Ekspresi lambda apa pun dapat dikonversi menjadi jenis delegasi. Jenis parameter dan nilai pengembaliannya menentukan jenis delegasi tempat ekspresi lambda dapat dikonversi. Jika ekspresi lambda tidak mengembalikan nilai, ekspresi tersebut dapat dikonversi ke salah satu jenis delegasi Action; jika tidak, ekspresi tersebut dapat dikonversi ke salah satu jenis delegasi Func. Misalnya, ekspresi lambda yang memiliki dua parameter dan tidak mengembalikan nilai dapat dikonversi menjadi delegasi Action<T1,T2>. Ekspresi lambda yang memiliki satu parameter dan mengembalikan nilai dapat dikonversi menjadi delegasi Func<T,TResult>. Dalam contoh berikut, ekspresi x => x * xlambda , yang menentukan parameter bernama x dan mengembalikan nilai x kuadrat, ditetapkan ke variabel jenis delegasi:

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

Ekspresi lambda juga dapat dikonversi ke tipe pohon ekspresi, seperti yang ditunjukkan contoh berikut:

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

Anda menggunakan ekspresi lambda dalam kode apa pun yang memerlukan instans jenis delegasi atau pohon ekspresi. Salah satu contohnya adalah argumen ke Task.Run(Action) metode untuk meneruskan kode yang harus dijalankan di latar belakang. Anda juga bisa menggunakan ekspresi lambda saat menulis LINQ di C#, seperti yang ditunjukkan contoh berikut:

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

Saat Anda menggunakan sintaks berbasis metode untuk memanggil Enumerable.Select metode di System.Linq.Enumerable kelas, misalnya di LINQ ke Objek dan LINQ ke XML, parameter adalah jenis delegasi System.Func<T,TResult>. Saat Anda memanggil metode Queryable.Select di kelas System.Linq.Queryable, misalnya di LINQ untuk SQL, jenis parameter adalah jenis pohon ekspresi Expression<Func<TSource,TResult>>. Dalam kedua kasus ini, Anda dapat menggunakan ekspresi lambda yang sama untuk menentukan nilai parameter. Itu membuat dua panggilan Select terlihat mirip meskipun sebenarnya jenis objek yang dibuat dari lambda berbeda.

Ekspresi lambda

Ekspresi lambda dengan ekspresi di sisi kanan operator => disebut ekspresi lambda. Ekspresi lambda mengembalikan hasil ekspresi dan mengambil bentuk dasar berikut:

(input-parameters) => expression

Tubuh ekspresi lambda dapat terdiri dari pemanggilan metode. Namun, jika Anda membuat pohon ekspresi yang dievaluasi di luar konteks .NET Common Language Runtime (CLR), seperti di SQL Server, Anda tidak boleh menggunakan pemanggilan metode dalam ekspresi lambda. Metode tidak memiliki arti di luar konteks .NET Common Language Runtime (CLR).

Lambda pernyataan

Pernyataan lambda menyerupai lambda ekspresi kecuali bahwa pernyataannya diapit kurung kurawal:

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

Isi dari pernyataan lambda dapat terdiri dari sejumlah pernyataan; namun, dalam praktiknya biasanya tidak ada lebih dari dua atau tiga.

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

Anda tidak bisa menggunakan pernyataan lambda untuk membuat pohon ekspresi.

Parameter input terhadap ekspresi lambda

Anda mengapit parameter input ekspresi lambda di dalam tanda kurung. Tentukan parameter input nol dengan tanda kurung yang kosong:

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

Jika ekspresi lambda hanya memiliki satu parameter input, tanda kurung adalah opsional:

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

Dua atau lebih parameter masukan dipisahkan dengan koma:

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

Terkadang kompiler tidak dapat menyimpulkan jenis parameter input. Anda dapat menentukan tipe secara eksplisit seperti yang ditunjukkan pada contoh berikut:

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

Jenis parameter input harus semua eksplisit atau semua implisit; jika tidak, akan muncul kesalahan penyusun CS0748.

Anda dapat menggunakan buang untuk menentukan dua parameter input atau lebih dari ekspresi lambda yang tidak digunakan dalam ekspresi:

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

Parameter buang Lambda dapat berguna saat Anda menggunakan ekspresi lambda untuk menyediakan penanganan aktivitas.

Catatan

Untuk kompatibilitas mundur, jika hanya satu parameter input bernama _, maka, dalam ekspresi lambda, _ diperlakukan sebagai nama parameter itu.

Dimulai dengan C# 12, Anda dapat memberikan nilai default untuk parameter pada ekspresi lambda. Sintaksis dan pembatasan pada nilai parameter default sama dengan untuk metode dan fungsi lokal. Contoh berikut mendeklarasikan ekspresi lambda dengan parameter default, lalu memanggilnya sekali menggunakan default dan sekali dengan dua parameter eksplisit:

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

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

Anda juga dapat mendeklarasikan ekspresi lambda dengan params array atau koleksi sebagai parameter:

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

Sebagai bagian dari pembaruan ini, ketika grup metode yang memiliki parameter default ditetapkan ke ekspresi lambda, ekspresi lambda tersebut juga memiliki parameter default yang sama. Grup metode dengan params parameter koleksi juga dapat ditetapkan ke ekspresi lambda.

Ekspresi Lambda dengan parameter atau params koleksi default karena parameter tidak memiliki jenis alami yang sesuai dengan Func<> atau Action<> jenis. Namun, Anda dapat menentukan jenis delegasi yang menyertakan nilai parameter default:

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

Atau, Anda dapat menggunakan variabel yang diketik secara implisit dengan var deklarasi untuk menentukan jenis delegasi. Pengkompilasi mensintesis jenis delegasi yang benar.

Untuk informasi selengkapnya tentang parameter default pada ekspresi lambda, lihat spesifikasi fitur untuk parameter default pada ekspresi lambda.

Lambda asinkron

Anda dengan mudah bisa membuat ekspresi dan pernyataan lambda yang menggabungkan pemrosesan asinkron dengan menggunakan kata kunci Asinkron dan Operator Await. Sebagai contoh, contoh Formulir Windows berikut berisi penanganan aktivitas yang memanggil dan menunggu metode asinkron, 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);
    }
}

Anda dapat menambahkan event handler yang sama dengan menggunakan lambda async. Untuk menambahkan handler ini, tambahkan pengubah async sebelum daftar parameter lambda, seperti yang ditunjukkan oleh contoh berikut:

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

Untuk informasi lebih lengkap tentang metode asinkron, lihat Pemrograman asinkron dengan Asinkron dan Await (Visual Basic).

Ekspresi dan tupel Lambda

Bahasa C# menyediakan dukungan bawaan untuk tuple. Anda bisa memberikan tuple sebagai argumen untuk ekspresi lambda, dan ekspresi lambda Anda juga dapat mengembalikan tuple. Dalam beberapa kasus, kompiler C# menggunakan inferensi tipe untuk menentukan tipe komponen tuple.

Anda mendefinisikan tupel dengan melampirkan daftar komponennya yang dipisahkan koma dalam tanda kurung. Contoh berikut ini menggunakan tuple dengan tiga komponen untuk meneruskan urutan angka ke ekspresi lambda, yang menggandakan setiap nilai dan mengembalikan tuple dengan tiga komponen yang berisi hasil perkalian.

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)

Biasanya, field dari sebuah tuple diberi nama Item1, Item2, dan seterusnya. Anda dapat, bagaimanapun, mendefinisikan sebuah tuple dengan komponen bernama, seperti contoh berikut.

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

Untuk informasi selengkapnya tentang tupel C#, lihat Jenis tupel.

Lambda dengan operator kueri standar

LINQ ke Objek, di antara implementasi lainnya, memiliki parameter input yang jenisnya adalah salah Func<TResult> satu keluarga delegasi generik. Delegasi ini menggunakan parameter tipe untuk menentukan jumlah dan tipe parameter input, dan tipe pengembalian delegasi. Func delegasi berguna untuk merangkum ekspresi yang ditentukan pengguna yang diterapkan ke setiap elemen dalam sekumpulan data sumber. Misalnya, pertimbangkan jenis delegasi Func<T,TResult>:

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

Delegasi dapat dipakai sebagai Func<int, bool>instans di mana int merupakan parameter input dan bool merupakan nilai kembalian. Nilai kembalian selalu ditentukan dalam parameter tipe terakhir. Misalnya, Func<int, string, bool> mendefinisikan delegasi dengan dua parameter input, int dan string, dan jenis pengembalian bool. Delegasi berikut Func , saat dipanggil, mengembalikan nilai Boolean yang menunjukkan apakah parameter input sama dengan lima:

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

Anda juga dapat menyediakan ekspresi lambda saat jenis argumen adalah Expression<TDelegate>, misalnya dalam operator kueri standar yang ditentukan dalam jenis Queryable. Saat Anda menentukan argumen Expression<TDelegate>, lambda dikompilasi ke pohon ekspresi.

Contoh ini menggunakan operator kueri standar Count:

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

Penyusun dapat menyimpulkan jenis parameter input, atau Anda juga dapat menentukannya secara eksplisit. Ekspresi lambda khusus ini menghitung bilangan bulat tersebut (n) yang saat dibagi dua memiliki sisa 1.

Contoh berikut menghasilkan urutan yang berisi semua elemen dalam array numbers yang mendahului 9, karena itu adalah angka pertama dalam urutan yang tidak memenuhi kondisi:

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

Contoh berikut menentukan beberapa parameter input dengan menyertakannya ke dalam tanda kurung. Metode mengembalikan semua elemen dalam array numbers hingga menemukan angka yang nilainya kurang dari posisi ordinalnya dalam array:

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

Anda tidak menggunakan ekspresi lambda secara langsung dalam ekspresi kueri, tetapi Anda bisa menggunakannya dalam panggilan metode dalam ekspresi kueri, seperti yang ditunjukkan pada contoh berikut:

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

Ketik inferensi dalam sebuah ekspresi lambda

Di saat menulis lambda, Anda sering tidak perlu menentukan jenis untuk parameter input karena penyusun dapat menyimpulkan jenis berdasarkan isi lambda, jenis parameter, dan faktor lainnya seperti yang dijelaskan dalam spesifikasi bahasa C#. Bagi sebagian besar operator kueri standar, input pertama adalah jenis elemen dalam urutan sumber. Jika Anda mengkueri IEnumerable<Customer>, maka variabel input disimpulkan sebagai objek Customer, yang berarti Anda memiliki akses ke metode dan propertinya:

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

Aturan umum untuk inferensi jenis untuk lambda adalah sebagai berikut ini:

  • Lambda harus berisikan jumlah parameter yang sama dengan jenis delegasi.
  • Setiap parameter input dalam lambda harus secara implisit dapat dikonversi ke parameter delegasi yang sudah sesuai.
  • Nilai kembalian lambda (jika ada) harus secara implisit dapat dikonversi ke tipe pengembalian delegasi.

Tipe alami dari ekspresi lambda

Ekspresi lambda itu sendiri tidak memiliki tipe karena sistem tipe umum tidak memiliki konsep intrinsik "ekspresi lambda." Namun, terkadang nyaman untuk berbicara secara informal tentang "tipe" ekspresi lambda. "Jenis" informal itu mengacu pada jenis atau jenis Expression delegasi tempat ekspresi lambda dikonversi.

Dimulai dengan C# 10, ekspresi lambda dapat memiliki jenis alami. Alih-alih memaksa Anda untuk mendeklarasikan jenis delegasi, seperti Func<...> atau Action<...> untuk ekspresi lambda, pengkompilasi dapat menyimpulkan jenis delegasi dari ekspresi lambda. Misalnya, pertimbangkan dua jenis deklarasi berikut:

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

Penyusun dapat menyimpulkan parse menjadi Func<string, int>. Penyusun memilih yang tersedia Func atau Action delegasi, jika ada yang cocok. Jika tidak, itu mensintesis tipe delegasi. Misalnya, jenis delegasi disintesis jika ekspresi lambda memiliki parameter ref. Ketika sebuah ekspresi lambda memiliki jenis alami, ekspresi tersebut dapat ditetapkan ke jenis yang kurang eksplisit, seperti System.Object atau System.Delegate:

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

Kelompok metode (yaitu, nama metode tanpa daftar parameter) dengan tepat satu kelebihan beban memiliki jenis alami:

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

Jika Anda menetapkan ekspresi lambda ke System.Linq.Expressions.LambdaExpression, atau System.Linq.Expressions.Expression, dan lambda memiliki jenis delegasi alami, ekspresi memiliki jenis alami System.Linq.Expressions.Expression<TDelegate>, dengan jenis delegasi alami yang digunakan sebagai argumen untuk parameter jenis:

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

Tidak semua ekspresi lambda memiliki tipe alami. Pertimbangkan dua jenis deklarasi berikut:

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

Penyusun tidak dapat menyimpulkan jenis parameter untuk s. Ketika kompiler tidak dapat menyimpulkan tipe alami, Anda harus mendeklarasikan tipenya:

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

Jenis pengembalian yang eksplisit

Biasanya, tipe pengembalian ekspresi lambda jelas dan disimpulkan. Untuk beberapa ekspresi yang tidak berfungsi:

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

Dimulai dengan C# 10, Anda dapat menentukan tipe kembalian ekspresi lambda sebelum parameter input. Saat Anda menentukan tipe pengembalian eksplisit, Anda harus memberi tanda kurung pada parameter input:

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

Atribut

Dimulai dengan C# 10, Anda dapat menambahkan atribut ke ekspresi lambda dan parameter-parameternya. Contoh berikut menunjukkan cara menambahkan atribut ke ekspresi lambda:

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

Anda juga dapat menambahkan atribut ke parameter input atau nilai kembalian, seperti yang ditunjukkan contoh berikut:

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

Seperti yang ditunjukkan contoh sebelumnya, Anda harus memberi tanda kurung pada parameter input saat Anda menambahkan atribut ke ekspresi lambda atau parameternya.

Penting

Ekspresi Lambda dipanggil melalui tipe delegasi yang mendasarinya. Yang berbeda dari metode dan fungsi lokal. Metode delegasi Invoke tidak memeriksa atribut dalam ekspresi lambda. Atribut tidak memiliki efek apa pun saat ekspresi lambda dipanggil. Atribut pada ekspresi lambda berguna untuk analisis kode, dan dapat ditemukan melalui refleksi. Salah satu konsekuensi dari keputusan ini adalah bahwa System.Diagnostics.ConditionalAttribute tidak bisa diterapkan pada ekspresi lambda.

Menangkap variabel luar dan cakupan variabel dalam ekspresi lambda

Lambda bisa merujuk ke variabel luar. Variabel luar ini adalah variabel yang berada dalam cakupan dalam metode yang menentukan ekspresi lambda, atau dalam cakupan dalam jenis yang berisi ekspresi lambda. Variabel yang ditangkap dengan cara ini disimpan untuk digunakan dalam ekspresi lambda bahkan jika variabel akan keluar dari ruang lingkup dan menjadi sampah yang dikumpulkan. Variabel luar harus ditetapkan secara pasti sebelum dapat dikonsumsi dalam ekspresi lambda. Contoh berikut menunjukkan persyaratan ini:

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
}

Aturan berikut berlaku untuk cakupan variabel dalam ekspresi lambda:

  • Variabel yang ditangkap tidak akan dikumpulkan hingga delegasi yang mereferensikannya memenuhi syarat untuk pengumpulan sampah.
  • Variabel yang diperkenalkan dalam ekspresi lambda tidak terlihat dalam metode terlampir.
  • Ekspresi lambda tidak bisa langsung mengambil parameter masuk, ref, atau keluar dari metode penutup.
  • Sebuah pernyataan pengembalian dalam ekspresi lambda tidak menyebabkan metode penutup kembali.
  • Ekspresi lambda tidak boleh berisi pernyataan goto, break, atau continue jika target pernyataan jump tersebut ada di luar blok ekspresi lambda. Ini juga merupakan kesalahan untuk memiliki pernyataan lompat di luar blok ekspresi lambda jika target berada di dalam blok.

Anda dapat menerapkan pengubah static ke ekspresi lambda untuk mencegah pengambilan variabel lokal atau status instans yang tidak disengaja oleh lambda:

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

Lambda statis tidak dapat mengambil variabel lokal atau status instans dari cakupan penutup, tetapi dapat mereferensikan anggota statis dan definisi konstanta.

Spesifikasi bahasa C#

Untuk informasi selengkapnya, lihat bagian Ekspresi fungsi anonim dari spesifikasi bahasa pemrogram C#.

Untuk informasi selengkapnya tentang fitur-fitur ini, lihat catatan proposal fitur berikut ini:

Lihat juga