Foto oleh Rashed Paykary via Pexels

Record adalah tipe referensi khusus yang diperkenalkan pada C# 9, dirancang khusus untuk merepresentasikan data yang sifatnya immutable (tidak berubah setelah dibuat) dengan kemampuan perbandingan nilai (value equality) bawaan, berbeda dengan class biasa yang secara default membandingkan berdasarkan referensi memori.

Pattern matching adalah kemampuan memeriksa bentuk atau struktur sebuah nilai dan mengekstrak bagian-bagiannya sekaligus dalam satu ekspresi, jauh lebih ringkas dibanding rangkaian if-else atau casting manual yang panjang untuk memeriksa tipe dan nilai suatu object.

Artikel ini membahas cara mendefinisikan record, perbedaannya dengan class biasa dari sisi equality dan immutability, serta berbagai bentuk pattern matching modern yang semakin sering dipakai dalam kode C# terkini.

Mendefinisikan Record dengan Sintaks Ringkas

Record membandingkan berdasarkan nilai, bukan referensi

public record Produk(string Nama, decimal Harga); Produk p1 = new Produk("Laptop", 8000000); Produk p2 = new Produk("Laptop", 8000000); Console.WriteLine(p1 == p2); -- Expected: true, karena VALUE EQUALITY // Berbeda dengan class biasa yang akan menghasilkan false
Dua record dengan nilai property yang identik akan dianggap sama ketika dibandingkan dengan ==, karena record secara otomatis mengimplementasikan value equality, berbeda dengan class biasa yang membandingkan apakah keduanya merujuk ke object yang sama persis di memori, meski isinya identik.

Immutability dan With Expression

With expression membuat salinan baru dengan sebagian nilai berbeda

Produk p1 = new Produk("Laptop", 8000000); Produk p2 = p1 with { Harga = 7500000 }; Console.WriteLine(p1.Harga); -- Expected: 8000000 (p1 TIDAK berubah) Console.WriteLine(p2.Harga); -- Expected: 7500000 (copy baru dengan Harga berbeda)
Record bersifat immutable secara default, sehingga untuk 'mengubah' nilainya, yang sebenarnya terjadi adalah pembuatan salinan baru dengan with expression, menyalin seluruh property dari object asli kecuali yang secara eksplisit diubah. Pendekatan ini menghindari bug akibat perubahan data yang tidak disengaja pada object yang dibagikan ke banyak bagian kode.

Type Pattern dan Property Pattern

Pattern matching memeriksa tipe dan struktur sekaligus

object obj = 42; if (obj is int angka && angka > 10) Console.WriteLine($"Angka besar: {angka}"); Produk p = new Produk("Laptop", 8000000); if (p is { Harga: > 5000000 }) Console.WriteLine("Produk mahal");
Type pattern memeriksa apakah sebuah object bertipe tertentu sekaligus mengekstrak nilainya ke variabel baru dalam satu ekspresi, sementara property pattern memeriksa nilai property tertentu dari sebuah object tanpa perlu menulis p.Harga > 5000000 secara terpisah, keduanya menghasilkan kode yang lebih ringkas dan ekspresif.

Switch Expression dengan Pattern Matching

Switch expression dengan property pattern untuk logika kompleks

string KategoriHarga(Produk p) => p switch { { Harga: >= 10000000 } => "Premium", { Harga: >= 5000000 } => "Menengah", { Harga: > 0 } => "Ekonomis", _ => "Tidak Valid" }; Console.WriteLine(KategoriHarga(new Produk("Laptop", 8000000))); -- Expected: Menengah
Menggabungkan switch expression dengan property pattern memungkinkan logika pengkategorian kompleks ditulis dengan sangat ringkas dan deklaratif, jauh lebih mudah dibaca dibanding rangkaian if-else-if yang panjang untuk memeriksa rentang nilai yang sama.

Positional Record dan With-Expression untuk Immutability

Record dengan with-expression menghasilkan salinan baru yang immutable

public record Pegawai(string Nama, string Departemen, decimal Gaji); var p1 = new Pegawai("Siti", "Finance", 7000000); var p2 = p1 with { Gaji = 7500000 }; Console.WriteLine(p1); // Output: Pegawai { Nama = Siti, Departemen = Finance, Gaji = 7000000 } Console.WriteLine(p2); // Output: Pegawai { Nama = Siti, Departemen = Finance, Gaji = 7500000 } Console.WriteLine(p1 == p2); // Output: False (value equality, bukan reference)

Record yang diperkenalkan pada C# 9.0 dirancang khusus untuk merepresentasikan data yang bersifat immutable, berbeda dengan class biasa yang propertinya lazim diubah setelah objek dibuat. Positional record seperti Pegawai pada contoh di atas otomatis menghasilkan constructor, property read-only, serta implementasi ToString() yang menampilkan seluruh nilai property secara terbaca, tanpa perlu menuliskan kode boilerplate tersebut secara manual seperti pada class konvensional.

With-expression (with { ... }) memungkinkan pembuatan salinan record baru dengan sebagian nilai property diubah tanpa memengaruhi objek record aslinya, seperti terlihat pada p2 yang hanya mengubah nilai Gaji sementara Nama dan Departemen tetap sama dengan p1. Pola ini sangat berguna untuk merepresentasikan perubahan data seiring waktu tanpa risiko side effect dari mutasi objek yang sedang dipakai di bagian lain program.

Record juga secara otomatis mengimplementasikan value equality, artinya dua instance record dianggap sama (menggunakan operator ==) jika seluruh nilai propertinya identik, berbeda dengan class biasa yang secara default membandingkan berdasarkan reference di memori. Fitur pattern matching modern seperti property pattern (pegawai is { Departemen: "Finance" }) juga bekerja sangat natural dengan record karena strukturnya yang predictable dan immutable.

Record Struct: Menggabungkan Semantik Record dengan Value Type

record struct berperilaku seperti record namun disimpan sebagai value type

public readonly record struct Koordinat(double Lintang, double Bujur); var titikA = new Koordinat(-6.2088, 106.8456); var titikB = titikA with { Bujur = 106.8000 }; Console.WriteLine(titikA); // Output: Koordinat { Lintang = -6.2088, Bujur = 106.8456 } Console.WriteLine(titikA == titikB); // Output: False (value equality berdasarkan isi field) Console.WriteLine(titikA.Equals(new Koordinat(-6.2088, 106.8456))); // Output: True

Record struct, diperkenalkan pada C# 10, menggabungkan semantik record seperti value equality otomatis, ToString() yang informatif, dan dukungan with-expression, dengan karakteristik penyimpanan memori milik struct yaitu sebagai value type yang disalin sepenuhnya saat di-assign ke variabel lain, berbeda dari record class biasa yang tetap merupakan reference type. Pada contoh di atas, Koordinat cocok dimodelkan sebagai record struct karena merepresentasikan data kecil dan sederhana yang secara konseptual memang berupa nilai, bukan entitas dengan identitas.

Kata kunci readonly yang ditambahkan sebelum record struct membuat seluruh property di dalamnya bersifat init-only secara otomatis, sehingga objek Koordinat benar-benar immutable setelah dibuat, sama seperti record class pada umumnya namun tanpa overhead alokasi pada heap yang biasanya menyertai reference type. With-expression tetap berfungsi normal pada record struct, menghasilkan salinan struct baru dengan sebagian field diubah tanpa memengaruhi struct aslinya.

Pemilihan antara record class dan record struct sebaiknya mengikuti pedoman umum pemilihan class versus struct, yaitu record struct lebih cocok untuk data berukuran kecil yang sering dibuat dan dibuang dalam jumlah besar seperti koordinat geografis atau rentang tanggal, sedangkan record class lebih cocok untuk entitas data yang lebih kompleks atau berukuran besar, karena penyalinan struct besar secara berulang justru dapat menurunkan performa dibanding sekadar menyalin reference pada record class. Tanpa kata kunci readonly, record struct tetap memperbolehkan property diubah melalui setter biasa setelah objek dibuat, sehingga pencantuman readonly secara eksplisit tetap diperlukan ketika sifat immutable benar-benar menjadi kebutuhan utama dari struktur data tersebut.

Pattern Matching Lanjutan: Type Pattern dan Property Pattern

object input = new { Status = "Aktif", Umur = 20 }; string hasil = input switch { int n when n > 0 => "Angka positif", string s => $"Teks: {s}", { } obj when obj.GetType().GetProperty("Status") != null => "Punya properti Status", _ => "Tipe tidak dikenali" }; Console.WriteLine(hasil); // Output: // Punya properti Status

Type pattern pada switch expression memungkinkan percabangan logika berdasarkan tipe data suatu object sekaligus melakukan casting secara implisit, seperti case string s yang langsung memberi variabel s bertipe string tanpa perlu casting manual seperti pada switch statement klasik. Klausa when menambahkan syarat kondisional tambahan di luar pengecekan tipe semata, sehingga satu case bisa memeriksa tipe sekaligus nilai dalam satu baris ekspresi yang ringkas dan mudah dibaca.

Property pattern memperluas kemampuan ini dengan memeriksa nilai dari properti tertentu pada suatu object secara langsung di dalam pattern, tanpa harus mengakses properti tersebut secara terpisah lebih dulu sebelum dibandingkan. Gabungan type pattern dan property pattern membuat logika percabangan berbasis struktur data menjadi lebih ringkas dibandingkan rangkaian if-else dan casting manual yang panjang, terutama ketika menangani banyak kemungkinan bentuk data yang berbeda dalam satu method.

Kesalahan Umum yang Sering Terjadi

Mengira Record Sepenuhnya Menggantikan Class

Record paling cocok untuk data yang sifatnya immutable seperti DTO atau value object, namun untuk entity dengan identitas dan siklus hidup yang kompleks serta behavior yang banyak, class biasa masih lebih sesuai dibanding record.

Mencoba Mengubah Property Record Secara Langsung

Record dengan positional parameter secara default memiliki property init-only, sehingga mencoba mengubahnya langsung setelah object dibuat (p1.Harga = 100) akan menghasilkan error kompilasi; gunakan with expression untuk membuat salinan dengan nilai berbeda.

Tidak Memanfaatkan Pattern Matching yang Sudah Tersedia

Developer yang terbiasa dengan gaya C# lama kadang tetap menulis casting manual dan if-else berantai panjang, padahal pattern matching modern bisa menyederhanakan kode tersebut secara signifikan dengan hasil yang sama.

Kapan Pendekatan Ini Tidak Cocok Dipakai

Record memberikan value equality dan immutability secara otomatis dengan sintaks yang sangat ringkas, sangat cocok untuk DTO, value object, atau data yang dikirim antar layer aplikasi tanpa perlu diubah setelah dibuat.

Pattern matching membuat kode pengecekan kondisi kompleks jauh lebih ringkas dan deklaratif, namun bagi developer yang belum terbiasa dengan sintaksnya, kode yang memakai pattern matching secara intensif bisa terasa kurang familiar dibanding gaya if-else tradisional di awal pembelajaran.

Ringkasan

Record dan pattern matching adalah fitur modern C# yang membuat kode lebih ringkas dan ekspresif: record untuk data immutable dengan value equality, dan pattern matching untuk memeriksa struktur data dengan sintaks yang deklaratif. Manfaatkan keduanya terutama untuk DTO dan logika pengkategorian data yang sering ditemui dalam aplikasi sehari-hari.

Seri Belajar C# Fundamental:

← Sebelumnya: Extension Method di C#

→ Selanjutnya: Best Practice Clean Code di C#