Operator 'with' pada C# Records Menciptakan Data Tidak Konsisten Ketika Digunakan dengan Computed Properties

Tim Komunitas BigGo
Operator 'with' pada C# Records Menciptakan Data Tidak Konsisten Ketika Digunakan dengan Computed Properties

Sebuah masalah signifikan dengan C# records telah muncul yang bahkan membuat developer berpengalaman terkejut. Masalah ini terjadi ketika menggunakan operator with bersamaan dengan computed properties, yang menyebabkan inkonsistensi data yang sulit untuk di-debug.

Masalah Inti dengan Nondestructive Mutation

C# records memperkenalkan operator with untuk nondestructive mutation, memungkinkan developer membuat instance baru dengan nilai properti yang dimodifikasi. Namun, operator ini tidak bekerja seperti yang diharapkan banyak developer. Alih-alih memanggil constructor dengan nilai baru, operator ini melakukan memory copy dari record asli dan kemudian langsung mengatur field yang ditentukan. Pendekatan ini melewati kalkulasi properti pada waktu inisialisasi.

Ketika sebuah record mengandung computed properties yang mendapatkan nilainya dari field lain selama inisialisasi, menggunakan with menciptakan objek yang tidak konsisten. Sebagai contoh, sebuah record yang menghitung apakah sebuah angka genap atau ganjil selama konstruksi akan mempertahankan nilai computed yang lama bahkan ketika angka dasarnya berubah melalui operasi with.

Computed properties: Properti yang menghitung nilainya berdasarkan data lain selama pembuatan objek, bukan diatur secara langsung.

Kode Demonstrasi Masalah:

public sealed record Number(int Value)
{
    public bool Even { get; } = (Value & 1) == 0;
}

var n2 = new Number(2);           // Even = True (benar)
var n3 = n2 with { Value = 3 };   // Even = True (salah, seharusnya False)

Perdebatan Komunitas Mengenai Perilaku yang Diharapkan

Komunitas developer tetap terbagi mengenai apakah ini merupakan bug atau perilaku yang diharapkan. Sebagian berpendapat bahwa implementasi saat ini mengikuti spesifikasi bahasa dan memprioritaskan performa melalui memory copying. Yang lain berargumen bahwa ini melanggar prinsip least astonishment, di mana developer secara alami mengharapkan objek baru memiliki computed properties yang terinisialisasi dengan benar.

Diskusi ini mengungkapkan bahwa masalah ini mempengaruhi banyak developer yang menemukannya secara independen, menunjukkan bahwa ini merupakan masalah usability yang nyata bukan kasus tepi.

Opsi Workaround yang Terbatas

Developer yang menghadapi masalah ini memiliki sedikit solusi yang memuaskan. Pendekatan paling langsung melibatkan menghindari operasi with pada record yang mengandung computed properties. Solusi alternatif termasuk menulis custom Roslyn analyzers untuk mendeteksi pola penggunaan yang bermasalah atau mengimplementasikan skema lazy initialization yang kompleks.

Namun, workaround ini menambah kompleksitas dan overhead maintenance yang signifikan. Pendekatan lazy initialization memerlukan manajemen manual nilai computed, sementara solusi berbasis analyzer hanya bekerja dalam lingkungan development tertentu.

Ini benar-benar menyebabkan struktur data yang tidak konsisten yang merupakan tempat bug hidup (dan potensi masalah keamanan dalam kasus terburuk).

Opsi Solusi Alternatif yang Tersedia:

  • Opsi 1: Hindari penggunaan operator with untuk record dengan computed properties
  • Opsi 2: Tulis analyzer Roslyn untuk mendeteksi pola penggunaan yang bermasalah
  • Opsi 3: Implementasikan lazy initialization dengan kelas wrapper Lazy<T>
  • Opsi 4: Ajukan perubahan spesifikasi bahasa (solusi jangka panjang)

Trade-off Performa vs Kebenaran

Implementasi saat ini memprioritaskan performa dengan menggunakan memory copying alih-alih rekonstruksi objek penuh. Pilihan desain ini mencerminkan paradigma pemrograman lama di mana optimasi mikrodetik lebih diutamakan daripada pengalaman developer dan konsistensi data.

Desain bahasa modern biasanya lebih mengutamakan kebenaran secara default, dengan optimasi performa tersedia sebagai opt-in eksplisit. Keputusan tim C# untuk memprioritaskan kecepatan daripada konsistensi telah menciptakan situasi di mana record dapat mengandung kombinasi data yang secara logis tidak mungkin.

Masalah ini menyoroti ketegangan yang lebih luas dalam desain bahasa antara kompatibilitas mundur, performa, dan ekspektasi developer. Meskipun perilaku ini secara teknis mengikuti spesifikasi, ini menciptakan kesenjangan signifikan antara bagaimana fitur tersebut tampak bekerja dan bagaimana sebenarnya berfungsi.

Situasi ini berfungsi sebagai pengingat bahwa bahkan fitur bahasa yang sudah mapan dapat mengandung jebakan halus yang mempengaruhi aplikasi dunia nyata. Developer yang bekerja dengan C# records harus mempertimbangkan dengan hati-hati apakah use case mereka melibatkan computed properties sebelum sangat bergantung pada operator with.

Referensi: UNEXPECTED INCONSISTENCY IN RECORDS