Tuple membungkus sejumlah kecil nilai yang jumlahnya tetap menjadi satu nilai, tanpa perlu mendeklarasikan class untuknya. ("Ana", 31) adalah tuple berisi sebuah string dan sebuah int. Tuple adalah cara yang lazim untuk mengembalikan dua atau tiga hal dari sebuah method, dan untuk menyimpan pasangan nilai berumur pendek di dalam method.
Membuat tuple
Tipenya juga ditulis dengan tanda kurung: (string, int) adalah tipe tuple yang elemen pertamanya string dan elemen keduanya int.
Output:
Ana is 31
(3, 4)
179.70
(Ana, 32)
Tanpa nama, elemennya adalah Item1, Item2, Item3 dan seterusnya. Tuple ini adalah struct System.ValueTuple, jadi menyimpannya ke variabel lain akan menyalin nilainya, dan mengubah salinannya tidak memengaruhi yang asli.
Elemen bernama
Item1 tidak menjelaskan apa isi nilainya. Beri nama pada elemennya, baik di tipe maupun di literal:
Output:
Ana, 31
Desk lamp costs 24.99
Ana
Ana
Nama adalah kemudahan saat kompilasi. Saat program berjalan, kedua tuple di atas hanyalah nilai ValueTuple<string, int> biasa, karena itulah menyimpan person ke tuple dengan nama berbeda tetap bisa dikompilasi: yang harus cocok hanya tipe elemen dan urutannya.
C# 7.1 dan sesudahnya juga menyimpulkan nama dari variabel di dalam literal, jadi var t = (name, age); memberimu t.name dan t.age tanpa harus menulisnya dua kali. Di C# 7.0, elemen-elemen itu hanya Item1 dan Item2.
Mengembalikan banyak nilai dari method
Inilah alasan utama tuple ditambahkan ke bahasa ini. Sebelumnya, pilihannya adalah parameter out, class sekali pakai, atau System.Tuple dengan Item1 yang tidak bernama.
Output:
Lowest 60, highest 95
Average 78.75
Nama elemen di tipe kembalian menjadi nama yang dilihat pemanggil. Bandingkan dengan method yang sama memakai parameter out:
static void Stats(int[] scores, out int min, out int max, out double average) { ... }
Stats(scores, out int min, out int max, out double avg);
Parameter out tetap menjadi konvensi untuk bentuk TryParse, saat sebuah bool menyatakan berhasil atau tidak dan nilainya dikembalikan bersamanya. Untuk "hitung beberapa hal lalu kembalikan semuanya", tuple lebih enak dibaca. Lihat ref dan out.
Dekonstruksi
Dekonstruksi membongkar tuple menjadi variabel-variabel terpisah dalam satu pernyataan:
Output:
Lima 24.5
Lima
24.5
2 1
Nama variabel di var (city, temp) bebas kamu tentukan; nama itu tidak harus sama dengan nama elemen tuple, karena dekonstruksi bekerja berdasarkan posisi. Swap (a, b) = (b, a) membuat tuple dari nilai lama lebih dulu, baru kemudian mengisinya, jadi tidak ada nilai yang hilang.
Class milikmu sendiri bisa mendukung dekonstruksi dengan mendeklarasikan method Deconstruct yang punya parameter out. Sejak C# 7 dengan .NET Core 2.0 atau sesudahnya, KeyValuePair juga punya method itu, jadi loop dictionary bisa membongkar setiap entri langsung di kepala loop:
class Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y) { X = x; Y = y; }
public void Deconstruct(out int x, out int y) { x = X; y = Y; }
}
var (x, y) = new Point(3, 4);
foreach (var (name, score) in scoresByName) // Dictionary<string, int>
{
Console.WriteLine($"{name}: {score}");
}
Bentuk foreach (var (a, b) in listOfTuples) yang sama juga berlaku untuk list berisi tuple. Mengambil seluruh tuple sebagai satu variabel loop lalu membaca elemen bernamanya, seperti di bagian berikutnya, juga sama baiknya.
Tuple di list, LINQ, dan dictionary
List berisi named tuple adalah cara ringan untuk menyimpan baris-baris data di dalam method:
Output:
Chloe 95
Ana 88
Ben 72
(Ana, B), (Chloe, A)
Ben
False
ValueTuple mengimplementasikan Equals dan GetHashCode elemen demi elemen, jadi (2, 7) yang dibuat di dua tempat berbeda menemukan entri dictionary yang sama. Ini menjadikan tuple key gabungan yang paling sederhana: tanpa class, tanpa fungsi hash yang ditulis manual, tanpa penggabungan string seperti row + ":" + col.
Kesamaan tuple
Equals membandingkan elemen demi elemen dan sudah berfungsi sejak tuple diperkenalkan:
Output:
True
False
C# 7.3 menambahkan == dan != untuk tuple, yang dikompilasi menjadi perbandingan elemen demi elemen yang sama dan juga mengabaikan nama:
if ((order.Status, order.Paid) == ("shipped", true)) { ... }
Console.WriteLine((1, "x") == (1, "x")); // True
System.Tuple vs ValueTuple
.NET 4 memperkenalkan System.Tuple, sebuah class yang dibuat dengan Tuple.Create. Class ini masih ada di framework dan masih muncul di kode dan API lama, karena itulah pencarian "C# tuple" sering mengarah ke sana.
ValueTuple (C# 7) | System.Tuple (.NET 4) | |
|---|---|---|
| Sintaks | (1, "a"), (int, string) | Tuple.Create(1, "a"), Tuple<int, string> |
| Jenis | struct (value type) | class (reference type) |
| Nama elemen | Ya | Tidak, hanya Item1, Item2 |
| Bisa diubah | Ya, elemennya berupa field | Tidak, property read-only |
| Dekonstruksi | Ya | Ya, lewat extension method |
Output:
Ben 25
(Ben, 25)
ToValueTuple() dan ToTuple() mengonversi di antara keduanya. Utamakan value tuple di kode baru: value tuple tidak mengalokasikan apa pun di heap dan bisa membawa nama.
Kapan memakai class atau record
Tuple paling cocok saat pengelompokannya bersifat lokal dan jelas: method mengembalikan dua angka, query LINQ membawa sepasang nilai ke langkah berikutnya, dictionary butuh key dua bagian. Beralihlah ke tipe bernama saat:
- Bentuk yang sama muncul di beberapa signature method public.
(string, string, int)di lima tempat adalah class yang belum ditulis. - Nilai-nilainya butuh perilaku, validasi, atau lebih dari tiga atau empat elemen.
- Datanya melewati batas seperti serialisasi. Nama tidak ada saat program berjalan, jadi Newtonsoft.Json menulis
Item1danItem2, danSystem.Text.Jsonmenulis{}karena elemen tuple berupa field, yang secara default dilewati.
Record di C# 9 memberimu hampir semua kemudahan tuple dengan nama yang sungguhan. record Score(string Name, int Points); sudah punya value equality, ToString yang mudah dibaca, dan dekonstruksi bawaan. Lihat record.
Kesalahan umum
- Mengira nama tetap ada saat program berjalan. Reflection dan serializer melihat
Item1,Item2(atau tidak melihat apa pun). - Mengubah salinan. Tuple adalah struct:
var t2 = t1; t2.Item1 = 5;tidak mengubaht1. - Tuple dengan banyak elemen. Lebih dari tiga atau empat, tipe bernama lebih enak dibaca.
- Tertukar urutan dekonstruksi. Dekonstruksi bekerja berdasarkan posisi: dengan tuple
(Name, Age),var (age, name) = person;memasukkan nama keage, dan karenavarmenyimpulkan tipenya, tidak ada peringatan sama sekali.
Pertanyaan yang sering diajukan
Bagaimana cara mengembalikan banyak nilai dari method di C#?
Kembalikan sebuah tuple: deklarasikan tipe kembaliannya sebagai (int Min, int Max) lalu return (lowest, highest);. Pemanggil membaca result.Min dan result.Max, atau melakukan dekonstruksi dengan var (min, max) = MinMax(data);. Alternatifnya adalah parameter out, yang cocok untuk pola TryParse, atau class atau record kecil saat hasilnya punya makna tersendiri.
Apa itu named tuple di C#?
Named tuple adalah tuple yang elemennya punya nama, bukan Item1, Item2: (string Name, int Age) person = ("Ana", 31); membuat kamu bisa menulis person.Name. Nama-nama itu hanya ada saat kompilasi; di baliknya tetap sebuah ValueTuple<string, int>, dan Item1 tetap bisa dipakai.
Apa bedanya Tuple dan ValueTuple di C#?
System.Tuple (sejak .NET 4) adalah class: dialokasikan di heap, immutable, dan elemennya selalu hanya Item1, Item2. System.ValueTuple (C# 7) adalah struct dengan field yang bisa diubah, mendukung nama elemen dan sintaks (a, b), dan itulah yang dibuat oleh setiap literal tuple. Pakai value tuple di kode baru.
Bagaimana cara kerja dekonstruksi tuple di C#?
var (name, age) = person; mendeklarasikan dua variabel dan mengisinya dengan elemen tuple sesuai urutan. Kamu juga bisa mengisi variabel yang sudah ada dengan (a, b) = (b, a), yang menukar nilainya, dan melewati elemen dengan discard _: var (_, age) = person;.
Bisakah tuple dibandingkan dengan == di C#?
Sejak C# 7.3, == dan != membandingkan tuple elemen demi elemen: (1, "a") == (1, "a") bernilai true. Nama diabaikan; yang penting hanya posisi dan nilainya. Sebelum C# 7.3, pakai Equals, yang diimplementasikan value tuple dengan cara yang sama.