Operator 'with' dalam C# Records Mencipta Data Tidak Konsisten Apabila Digunakan dengan Computed Properties

Pasukan Komuniti BigGo
Operator 'with' dalam C# Records Mencipta Data Tidak Konsisten Apabila Digunakan dengan Computed Properties

Satu isu penting dengan C# records telah muncul yang mengejutkan walaupun pembangun berpengalaman. Masalah ini berlaku apabila menggunakan operator with bersama computed properties, yang membawa kepada ketidakkonsistenan data yang sukar untuk didebug.

Masalah Utama dengan Nondestructive Mutation

C# records memperkenalkan operator with untuk nondestructive mutation, membolehkan pembangun mencipta instance baharu dengan nilai property yang diubah suai. Walau bagaimanapun, operator ini tidak berfungsi seperti yang dijangkakan oleh ramai pembangun. Daripada memanggil constructor dengan nilai baharu, ia melakukan salinan memori rekod asal dan kemudian menetapkan field yang dinyatakan secara langsung. Pendekatan ini memintas pengiraan property pada masa inisialisasi.

Apabila rekod mengandungi computed properties yang memperoleh nilai mereka daripada field lain semasa inisialisasi, menggunakan with mencipta objek yang tidak konsisten. Sebagai contoh, rekod yang mengira sama ada nombor adalah genap atau ganjil semasa pembinaan akan mengekalkan nilai computed yang lama walaupun nombor asas berubah melalui operasi with.

Computed properties: Properties yang mengira nilai mereka berdasarkan data lain semasa penciptaan objek, bukannya ditetapkan secara langsung.

Kod Demonstrasi Masalah:

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

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

Perdebatan Komuniti Mengenai Tingkah Laku Yang Dijangkakan

Komuniti pembangun kekal berpecah sama ada ini merupakan bug atau tingkah laku yang dijangkakan. Sesetengah pihak berhujah bahawa pelaksanaan semasa mengikuti spesifikasi bahasa dan mengutamakan prestasi melalui penyalinan memori. Yang lain menegaskan bahawa ia melanggar prinsip least astonishment, di mana pembangun secara semula jadi menjangkakan objek baharu mempunyai computed properties yang diinisialisasi dengan betul.

Perbincangan telah mendedahkan bahawa isu ini mempengaruhi beberapa pembangun yang menemuinya secara bebas, menunjukkan ia mewakili masalah kebolehgunaan yang tulen dan bukannya kes tepi.

Pilihan Penyelesaian Terhad

Pembangun yang menghadapi isu ini mempunyai sedikit penyelesaian yang memuaskan. Pendekatan yang paling mudah melibatkan mengelakkan operasi with pada rekod yang mengandungi computed properties. Penyelesaian alternatif termasuk menulis custom Roslyn analyzers untuk mengesan corak penggunaan yang bermasalah atau melaksanakan skim lazy initialization yang kompleks.

Walau bagaimanapun, penyelesaian ini menambah kerumitan dan overhed penyelenggaraan yang ketara. Pendekatan lazy initialization memerlukan pengurusan manual nilai computed, manakala penyelesaian berasaskan analyzer hanya berfungsi dalam persekitaran pembangunan tertentu.

Ia benar-benar membawa kepada struktur data yang tidak konsisten di mana bug hidup (dan isu keselamatan yang berpotensi dalam kes yang terburuk).

Pilihan Penyelesaian Alternatif yang Tersedia:

  • Pilihan 1: Elakkan penggunaan operator with untuk rekod dengan sifat-sifat yang dikira
  • Pilihan 2: Tulis penganalisis Roslyn untuk mengesan corak penggunaan yang bermasalah
  • Pilihan 3: Laksanakan permulaan lewat dengan kelas pembalut Lazy<T>
  • Pilihan 4: Minta perubahan spesifikasi bahasa (penyelesaian jangka panjang)

Trade-off Prestasi vs Ketepatan

Pelaksanaan semasa mengutamakan prestasi dengan menggunakan penyalinan memori daripada pembinaan semula objek penuh. Pilihan reka bentuk ini mencerminkan paradigma pengaturcaraan lama di mana pengoptimuman mikrosaat mengambil keutamaan berbanding pengalaman pembangun dan konsistensi data.

Reka bentuk bahasa moden biasanya memihak kepada ketepatan secara lalai, dengan pengoptimuman prestasi tersedia sebagai opt-in eksplisit. Keputusan pasukan C# untuk mengutamakan kelajuan berbanding konsistensi telah mencipta situasi di mana rekod boleh mengandungi kombinasi data yang mustahil secara logik.

Isu ini menyerlahkan ketegangan yang lebih luas dalam reka bentuk bahasa antara keserasian ke belakang, prestasi, dan jangkaan pembangun. Walaupun tingkah laku secara teknikal mengikuti spesifikasi, ia mencipta jurang yang ketara antara bagaimana ciri itu kelihatan berfungsi dan bagaimana ia sebenarnya berfungsi.

Situasi ini berfungsi sebagai peringatan bahawa walaupun ciri bahasa yang mantap boleh mengandungi gotcha halus yang mempengaruhi aplikasi dunia sebenar. Pembangun yang bekerja dengan C# records harus mempertimbangkan dengan teliti sama ada kes penggunaan mereka melibatkan computed properties sebelum bergantung banyak pada operator with.

Rujukan: UNEXPECTED INCONSISTENCY IN RECORDS