Menu

Tuple C#: Named Tuple, Mengembalikan Banyak Nilai, Dekonstruksi

Tuple C# mengelompokkan beberapa nilai menjadi satu tanpa perlu mendeklarasikan tipe. Pelajari sintaks value tuple, elemen bernama, mengembalikan banyak nilai dari method, dekonstruksi dengan var (a, b), tuple sebagai key dictionary, dan perbedaan ValueTuple dengan System.Tuple yang lebih lama.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

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>
Jenisstruct (value type)class (reference type)
Nama elemenYaTidak, hanya Item1, Item2
Bisa diubahYa, elemennya berupa fieldTidak, property read-only
DekonstruksiYaYa, 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 Item1 dan Item2, dan System.Text.Json menulis {} 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 mengubah t1.
  • 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 ke age, dan karena var menyimpulkan 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.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai