Topik ini membahas record, yaitu tipe yang dideklarasikan dengan kata kunci record dan menyelesaikan tugas rutin tipe data secara otomatis. Materinya mencakup deklarasi record posisional, kesetaraan nilai yang membandingkan isi bukan alamat, serta imutabilitas (immutability, sifat data yang tidak dapat diubah setelah dibuat) beserta ekspresi with untuk membuat salinan yang dimodifikasi. Setelah topik ini diselesaikan, tipe data ringan dengan kesetaraan yang benar dapat dideklarasikan dalam satu baris.
Deklarasi record posisional#
Bentuk paling ringkasnya adalah record posisional, yang mendeklarasikan seluruh datanya dalam tanda kurung setelah nama tipenya. Satu baris record Titik(int X, int Y); cukup untuk menghasilkan konstruktor dua parameter, property X dan Y bersifat { get; init; }, serta metode ToString(), Equals, dan GetHashCode yang saling konsisten. Kompilator yang menyusun anggota-anggota tersebut inilah pembeda record dari kelas biasa.
Property hasil deklarasi posisional menggunakan aksesor init, sehingga nilainya ditetapkan saat konstruksi dan tertutup setelahnya. Anggota lain yang otomatis dibuat adalah metode Deconstruct, yang membongkar nilai record ke variabel terpisah melalui var (x, y) = p1;. Record tanpa daftar posisi juga sah, yaitu record Nama { ... } dengan property yang ditulis manual, dan bentuk ini dipakai bila anggotanya tidak sederhana. Sejak C# 10 tersedia pula record struct untuk record yang menyalin nilainya, sedangkan pada modul ini yang dibahas adalah record bertipe reference, yaitu bentuk bakunya.
Kesetaraan nilai#
Record mendefinisikan kesetaraan berdasarkan nilai (value equality), yaitu dua record dianggap sama apabila tipenya sama dan seluruh nilainya sama. Operator ==, !=, dan metode Equals semuanya mengikuti aturan ini tanpa penulisan tambahan. Perilaku ini berbeda dari kelas biasa, yang membandingkan reference dan menganggap dua object terpisah selalu berbeda.
// Program: record posisional Titik dan kesetaraan nilainya.
var p1 = new Titik(2, 3);
var p2 = new Titik(2, 3);
var p3 = new Titik(5, 1);
Console.WriteLine(p1 == p2);
Console.WriteLine(p1 == p3);
Console.WriteLine(p1);
record Titik(int X, int Y);True
False
Titik { X = 2, Y = 3 }p1 dan p2 dibuat melalui dua pemanggilan new yang berbeda, tetapi keluarannya True karena nilai X dan Y-nya identik. Baris ketiga menampilkan hasil metode ToString() yang dibuat kompilator, yaitu nama tipe beserta seluruh nilainya, sehingga isi record dapat diperiksa tanpa menulis pemformatnya. Operator != mengikuti aturan yang sama dan tinggal membalik hasilnya.
Kesetaraan nilai juga berlaku pada GetHashCode, yang dihitung kompilator dari seluruh nilainya sehingga dua record yang sama menghasilkan kode hash yang sama. Aturan ini penting ketika record disimpan dalam koleksi berbasis hash, karena posisi pencariannya bergantung pada kode hash tersebut. Perubahan satu nilai mengubah kode hashnya, dan di sinilah imutabilitas record menemukan alasan teknisnya.
Kontras berikut menunjukkan kelas biasa dengan data yang sama persis. Strukturnya sengaja dibuat setara dengan record Titik agar perbedaan keluarannya murni berasal dari deklarasinya. Keduanya menyimpan data yang sama, tetapi satu dideklarasikan sebagai kelas dan yang lainnya sebagai record.
var a1 = new Angka(2);
var a2 = new Angka(2);
Console.WriteLine(a1 == a2);
Console.WriteLine(a1.Equals(a2));
class Angka
{
public int Nilai;
public Angka(int nilai)
{
Nilai = nilai;
}
}False
FalseKedua pembanding menghasilkan False karena kelas Angka tidak mendefinisikan kesetaraan nilainya, sehingga perbandingannya jatuh pada reference yang memang berbeda. Dua nilai yang tampak sama pun tetap diperlakukan berbeda karena alamat objectnya berbeda. Memperoleh perilaku yang sama dengan record memerlukan penulisan Equals dan operator == secara manual, dan justru pekerjaan itulah yang dihapus oleh record.
Imutabilitas dan ekspresi with#
Property hasil deklarasi posisional bersifat init-only, sehingga perubahan nilai setelah konstruksi ditolak. Penolakan itu terjadi pada saat kompilasi, jauh sebelum program berjalan. Contoh berikut memakai record Titik yang sama dengan program di awal topik.
// tidak-dijalankan
p1.X = 9;Kompilator menolak baris tersebut dengan error CS8852: Init-only property or indexer 'Titik.X' can only be assigned in an object initializer. Pesan errornya menunjuk property yang bersangkutan beserta syarat penugasannya, sehingga penyebabnya langsung terbaca. Perubahan dinyatakan dengan ekspresi with, yaitu membuat record baru yang menyalin seluruh nilai sumbernya lalu mengganti anggota yang disebutkan.
var p1 = new Titik(2, 3);
var p2 = p1 with { Y = 7 };
Console.WriteLine(p1);
Console.WriteLine(p2);
Console.WriteLine(p1 == p2);
record Titik(int X, int Y);Titik { X = 2, Y = 3 }
Titik { X = 2, Y = 7 }
FalseEkspresi p1 with { Y = 7 } menghasilkan record baru dengan X yang disalin dari p1 dan Y yang diganti, sedangkan p1 tetap tidak berubah. Kesetaraan False pada baris ketiga konsisten dengan aturan kesetaraan nilai, karena nilai kedua record memang berbeda. Pola ini menjaga keamanan data yang dibagikan, karena tidak ada jalur untuk mengubah record yang sudah ada.
Ekspresi with sebenarnya bekerja pada tipe apa pun yang memiliki aksesor init atau set, tetapi manfaatnya paling terasa pada record karena seluruh anggotanya disalin otomatis. Salinan yang dihasilkan adalah object baru, sehingga pembaruan berantai menambah penggunaan memori pada setiap tahapnya. Data besar yang sering diperbarui menjadi pertimbangan untuk kembali ke kelas biasa dengan setter, sedangkan data kecil yang dibandingkan dan dibagikan sangat cocok dengan record.
Kesalahan umum#
- Mengharapkan perbandingan record berperilaku seperti kelas. Operator
==pada record membandingkan nilai, sehingga dua record berisi data sama menghasilkanTrue. Bila identitas object yang dibutuhkan, gunakanReferenceEqualsyang tetap membandingkan reference. - Menugaskan ulang property record setelah konstruksi. Property posisional bersifat init-only, sehingga muncul
error CS8852. Nyatakan perubahannya melalui ekspresiwithyang menghasilkan record baru. - Menyimpan anggota yang dapat berubah di dalam record. Kesetaraan dan
ToStringhanya mencerminkan anggota yang dideklarasikan, sedangkan isi koleksi di dalamnya dapat berubah tanpa terdeteksi. Gunakan anggota tak berubah agar jaminan record utuh.
Ringkasan#
- Record posisional mendeklarasikan datanya dalam satu baris dan memperoleh konstruktor, property init-only,
ToString, sertaEqualsdari kompilator. - Kesetaraan record adalah kesetaraan nilai, sehingga dua record dengan data sama dianggap sama tanpa penulisan tambahan.
- Ekspresi
withmembuat salinan yang dimodifikasi tanpa menyentuh record aslinya, dan inilah cara sah mengubah data record. - Record menjawab kebutuhan tipe data yang dibandingkan berdasarkan isinya, sedangkan kelas biasa tetap membandingkan reference.
Referensi#
- Records — Microsoft Learn — Menjelaskan record posisional, anggota yang dibuat kompilator, dan kesetaraan nilainya.
- with expression — Microsoft Learn
— Mendokumentasikan ekspresi
withuntuk menyalin record dengan perubahan tertentu. - Equality operators — Microsoft Learn — Menjelaskan perilaku operator kesetaraan, termasuk perbandingan pada tipe reference.