Baca dalam bahasa Inggris

Bagikan melalui


Ekspresi Lambda dan fungsi anonim

Anda menggunakan ekspresi lambda untuk membuat fungsi anonim. Gunakan operator deklarasi lambda => untuk memisahkan daftar parameter lambda dari isinya. 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 input (jika ada) di sisi kiri operator lambda dan ekspresi atau blok pernyataan di sisi lain.

Ekspresi lambda apa pun dapat dikonversi ke delegasi jenis . 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, itu 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 ke delegasi Func<T,TResult>. Dalam contoh berikut, ekspresi lambda x => x * x, 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

Lambda ekspresi juga dapat dikonversi ke jenis 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 metode Task.Run(Action) untuk meneruskan kode yang harus dijalankan di latar belakang. Anda juga dapat 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 metode Enumerable.Select di kelas System.Linq.Enumerable, 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 ke SQL, jenis parameter adalah jenis pohon ekspresi Expression<Func<TSource,TResult>>. Dalam kedua kasus, Anda dapat menggunakan ekspresi lambda yang sama untuk menentukan nilai parameter. Itu membuat dua panggilan Select terlihat mirip, walaupun sebenarnya jenis-jenis objek yang dibuat dari fungsi lambda berbeda.

Ekspresi lambda

Ekspresi lambda dengan suatu ekspresi di sebelah kanan operator => disebut ekspresi lambda . Ekspresi lambda mengembalikan hasil ekspresi dan mengambil formulir dasar berikut:

(input-parameters) => expression

Isi ekspresi lambda dapat terdiri dari panggilan 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 panggilan metode dalam ekspresi lambda. Metode tidak memiliki arti di luar konteks .NET Common Language Runtime (CLR).

Pernyataan lambda

Pernyataan lambda menyerupai ekspresi lambda, kecuali pernyataannya diapit oleh kurung kurawal.

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

Isi 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 dapat menggunakan pernyataan lambda untuk membuat pohon ekspresi.

Parameter masukan dari ekspresi lambda

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

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

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

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

Dua parameter input atau lebih dipisahkan oleh koma:

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

Terkadang pengkompilasi tidak dapat menyimpulkan jenis parameter input. Anda dapat menentukan jenis secara eksplisit seperti yang ditunjukkan dalam 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 terjadi kesalahan kompilasi CS0748.

Anda dapat menggunakan membuang untuk menentukan dua atau beberapa parameter input 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 parameter input tunggal yang diberi nama _, maka, dalam ekspresi lambda, _ diperlakukan sebagai nama parameter tersebut.

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 array atau koleksi params 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 parameter pengumpulan params juga dapat ditetapkan ke ekspresi lambda.

Ekspresi Lambda dengan parameter default atau koleksi params sebagai parameter tidak memiliki jenis alami yang sesuai dengan jenis Func<> atau Action<>. 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 deklarasi var 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.

Asinkron lambda

Anda dapat dengan mudah membuat ekspresi dan pernyataan lambda yang menggabungkan pemrosesan asinkron dengan menggunakan asinkron dan menunggu kata kunci. Misalnya, contoh Windows Forms 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 penanganan aktivitas yang sama dengan menggunakan lambda asinkron. Untuk menambahkan handler ini, tambahkan pengubah async sebelum daftar parameter lambda, seperti yang ditunjukkan 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 selengkapnya tentang cara membuat dan menggunakan metode asinkron, lihat Pemrograman Asinkron dengan asinkron dan menunggu.

Ekspresi lambda dan tuple

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

Anda menentukan tuple dengan menyertakan daftar komponennya yang dipisahkan koma dalam tanda kurung. Contoh berikut 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, bidang tuple diberi nama Item1, Item2, dan sebagainya. Anda dapat mendefinisikan tuple dengan komponen bernama, seperti yang dilakukan pada 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 tuple C#, lihat tipe tuple.

Lambdas dengan operator kueri standar

LINQ to Objects, di antara implementasi lainnya, memiliki parameter input yang jenisnya adalah salah satu keluarga Func<TResult> delegate generik. Delegasi ini menggunakan parameter jenis untuk menentukan jumlah dan jenis parameter input, serta jenis nilai yang dikembalikan oleh delegasi. Func delegat berguna untuk merangkum ekspresi yang ditentukan pengguna yang diterapkan pada setiap elemen dalam kumpulan data sumber. Misalnya, pertimbangkan jenis delegasi Func<T,TResult>:

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

Delegasi dapat dibuat sebagai instans Func<int, bool> di mana int adalah parameter input dan bool adalah nilai pengembalian. Nilai yang dikembalikan selalu ditentukan dalam parameter jenis terakhir. Misalnya, Func<int, string, bool> menentukan delegasi dengan dua parameter input, int dan string, dan jenis pengembalian bool. Delegasi Func berikut, 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 berikut 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)}");

Pengkompilasi dapat menyimpulkan jenis parameter input, atau Anda juga dapat menentukannya secara eksplisit. Ekspresi lambda khusus ini menghitung bilangan bulat tersebut (n) yang ketika 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 dalam tanda kurung. Metode mengembalikan semua elemen dalam array numbers sampai 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 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

Inferensi tipe dalam ekspresi lambda

Saat menulis lambda, Anda sering tidak perlu menentukan jenis untuk parameter input karena pengkompilasi dapat menyimpulkan jenis berdasarkan isi lambda, jenis parameter, dan faktor lain seperti yang dijelaskan dalam spesifikasi bahasa C#. Untuk 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:

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

Jenis alami ekspresi lambda

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

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 deklarasi berikut:

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

Pengkompilasi dapat menyimpulkan parse menjadi Func<string, int>. Pengkompilasi memilih delegasi Func atau Action yang tersedia, jika ada yang cocok. Jika tidak, itu mensintesis jenis delegasi. Misalnya, jenis delegasi disintesis jika ekspresi lambda memiliki parameter ref. Ketika 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>

Grup metode (yaitu, nama metode tanpa daftar parameter) dengan hanya satu overload 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 untuk 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 jenis alami. Pertimbangkan deklarasi berikut:

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

Pengkompilasi tidak dapat menyimpulkan jenis parameter untuk s. Ketika pengkompilasi tidak dapat menyimpulkan jenis alami, Anda harus mendeklarasikan jenisnya:

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

Jenis pengembalian eksplisit

Biasanya, jenis 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

Anda dapat menentukan jenis pengembalian ekspresi lambda sebelum parameter input. Saat Anda menentukan jenis pengembalian eksplisit, Anda harus memberi tanda kurung pada parameter input:

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

Atribut

Anda dapat menambahkan atribut ke ekspresi lambda dan 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 mengembalikan nilai, 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 mengurung dengan tanda kurung parameter input saat menambahkan atribut ke ekspresi lambda atau parameter dari ekspresi tersebut.

Penting

Ekspresi Lambda dijalankan melalui tipe delegat yang mendasari. Itu berbeda dari metode dan fungsi lokal. Fungsi Invoke delegasi tidak memeriksa atribut yang terdapat di ekspresi lambda. Atribut tidak memiliki efek apa pun saat ekspresi lambda dipanggil. Atribut pada ekspresi lambda berguna untuk analisis kode, dan dapat ditemukan melalui pantulan. Salah satu konsekuensi dari keputusan ini adalah bahwa System.Diagnostics.ConditionalAttribute tidak dapat diterapkan pada ekspresi lambda.

Pengambilan variabel luar dan cakupan variabel dalam ekspresi lambda

Lambda dapat 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 tersebut seharusnya keluar dari cakupan dan menjadi objek pengumpulan sampah. Variabel luar harus benar-benar ditetapkan sebelum dapat dikonsumsi dalam ekspresi lambda. Contoh berikut menunjukkan aturan 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 sampah sampai delegasi yang mereferensikannya menjadi memenuhi syarat untuk pengumpulan sampah.
  • Variabel yang diperkenalkan dalam ekspresi lambda tidak terlihat dalam metode penutup.
  • Ekspresi lambda tidak dapat langsung mengambil di, ref, atau parameter dari metode penutup.
  • Pernyataan mengembalikan dalam ekspresi lambda tidak menyebabkan metode penutup kembali.
  • Ekspresi lambda tidak boleh berisi goto, break, atau lanjut jika target pernyataan lompat tersebut berada di luar blok ekspresi lambda. Kesalahan juga terjadi jika ada pernyataan lompat di luar blok ekspresi lambda apabila 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 menangkap variabel lokal atau status instans dari lingkup pengapit, tetapi dapat merujuk anggota statis dan definisi konstanta.

Spesifikasi bahasa C#

Untuk informasi selengkapnya, lihat bagian ekspresi fungsi Anonim dari spesifikasi bahasa C#.

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

Lihat juga