Kesetaraan berbasis isi adalah alasan utama record diciptakan. Pertanyaannya, kapan persisnya dua record dianggap sama, dan apa saja yang compiler periksa? Record membandingkan tipe runtime terlebih dahulu, lalu membandingkan setiap property satu per satu. Aturan tersebut membuat record aman dipakai sebagai kunci pencarian di koleksi, sekaligus menyimpan satu kejutan pada pewarisan yang perlu kita kenali.

Kesetaraan Berbasis Isi #

Contoh berikut menguji beberapa skenario kesetaraan pada record Pesanan, termasuk pencarian di List:

csharp
public record Pesanan(int Id, string Produk, int Harga);

Pesanan pesananA = new Pesanan(1, "Kopi Gayo", 45000);
Pesanan pesananB = new Pesanan(1, "Kopi Gayo", 45000);
Pesanan pesananC = new Pesanan(1, "Kopi Gayo", 50000);

Console.WriteLine(pesananA == pesananB);
Console.WriteLine(pesananA == pesananC);
Console.WriteLine(pesananA.GetHashCode() == pesananB.GetHashCode());

List<Pesanan> antrean = new List<Pesanan> { pesananA };
Console.WriteLine(antrean.Contains(new Pesanan(1, "Kopi Gayo", 45000)));

Output yang Dihasilkan:

text
True
False
True
True

Mari kita bedah baris demi baris:

  1. pesananA == pesananB: seluruh property bernilai sama, sehingga perbandingan menghasilkan True; kedua record tidak perlu dibuat dari object yang sama.
  2. pesananA == pesananC: satu property berbeda, yaitu Harga, sudah cukup membuat keseluruhan perbandingan menghasilkan False.
  3. pesananA.GetHashCode() == pesananB.GetHashCode(): GetHashCode dihitung dari isi property; dua record yang setara wajib menghasilkan kode hash yang sama, dan hasilnya True.
  4. antrean.Contains(...): List memanggil Equals saat mencari; record baru dengan isi sama ditemukan meskipun dibuat terpisah dari yang tersimpan.

Pemeriksaan Tipe Runtime #

Method Equals yang dibangkitkan compiler juga membandingkan tipe runtime kedua object melalui anggota internal bernama EqualityContract. Contoh berikut membuktikan akibatnya:

csharp
public record PesananEkspres(int Id, string Produk, int Harga, int BiayaKurir)
    : Pesanan(Id, Produk, Harga);

Pesanan biasa = new Pesanan(1, "Kopi Gayo", 45000);
PesananEkspres ekspres = new PesananEkspres(1, "Kopi Gayo", 45000, 0);

Console.WriteLine(biasa == ekspres);

Output yang Dihasilkan:

text
False

ekspres mewarisi semua nilai dasar yang sama dan BiayaKurir bernilai 0. Meskipun demikian, tipe runtime PesananEkspres berbeda dari Pesanan, sehingga perbandingan menghasilkan False.

Cara Kerja Method Equals yang Dibangkitkan #

Method Equals hasil bangunan compiler menjalankan pemeriksaan berurutan berikut:

  1. Pemeriksaan null: parameter yang bernilai null langsung menghasilkan False.
  2. Pemeriksaan tipe: method membandingkan EqualityContract kedua object; tipe runtime yang berbeda langsung menghasilkan False.
  3. Perbandingan property: setiap property dibandingkan satu per satu dengan operator kesetaraannya; satu property berbeda membuat keseluruhan hasil menjadi False.

Compiler juga mengimplementasikan interface IEquatable<T> untuk tipe record tersebut, sehingga pembandingan tidak perlu melakukan cast dan bekerja cepat di dalam koleksi generik seperti List<T> maupun HashSet<T>. Manfaatnya terasa pada skenario pencarian: method Contains, IndexOf, dan Remove pada List memakai Equals yang sama dengan operator ==, sehingga hasil pencarian konsisten di semua jalur.