Menyalin data di C# mengikuti dua aturan berbeda. Value type (struct, int, bool, enum) disalin utuh berdasarkan nilai, sedangkan reference type (class, array, string, delegate) hanya menyalin alamatnya. Perbedaan ini disebut value semantics dan reference semantics, dan keduanya menentukan jumlah objek yang muncul di managed heap. Salah membaca aturan ini adalah sumber kejutan memori yang paling sering dialami pemula.

Contoh: Penyalinan Struct versus Class #

csharp
var a = new Titik { X = 1, Y = 2 };
var b = a;
b.X = 100;
Console.WriteLine($"struct: a=({a.X}, {a.Y}), b=({b.X}, {b.Y})");

var p = new TitikRef { X = 1, Y = 2 };
var q = p;
q.X = 100;
Console.WriteLine($"class : p=({p.X}, {p.Y}), q=({q.X}, {q.Y})");

Output yang Dihasilkan:

text
struct: a=(1, 2), b=(100, 2)
class : p=(100, 2), q=(100, 2)

Mari kita bedah baris demi baris:

  1. var b = a;: runtime menyalin seluruh isi struct a ke variabel baru b. Kini ada dua salinan data yang berdiri sendiri, sehingga perubahan pada b tidak menyentuh a.
  2. b.X = 100;: hanya salinan b yang berubah. Output membuktikan a tetap bernilai (1, 2).
  3. var q = p;: yang disalin hanyalah referensi (alamat) ke objek yang sama di heap. Tidak ada objek kedua yang dibuat.
  4. q.X = 100;: perubahan menjangkau satu-satunya objek di heap, sehingga p dan q melaporkan nilai yang sama.

Implikasi ke Memori #

Reference semantics membuat penggunaan memori hemat: satu objek besar dapat dipakai banyak variabel tanpa penyalinan data. Value semantics membuat data mandiri: setiap penugasan menduplikasi isi struct, dan struct berukuran besar menyalin banyak byte setiap kali berpindah variabel atau parameter. Runtime juga melakukan boxing ketika value type dipaksa menjadi objek di heap, dan setiap boxing menciptakan pekerjaan baru bagi garbage collector.

SituasiPilihan yang hemat memori
data kecil, sering disalinstruct kecil (sekitar 16-24 byte)
data besar, dibagikanclass yang diakses lewat referensi
struct besar yang sering lewat parameterkirim dengan in agar tanpa salinan