Value Type dan Reference Type di C#

Pada dasarnya C# memiliki empat type, yaitu value types, reference type, generic type parameters, dan pointer types. Dari keempat type tersebut, value type dan reference type merupakan kategori utama yang…

Pada dasarnya C# memiliki empat type, yaitu value types, reference type, generic type parameters, dan pointer types. Dari keempat type tersebut, value type dan reference type merupakan kategori utama yang sudah pasti digunakan sekalipun dalam program paling sederhana.

Value type mencakup berbagai built in types, terutama seluruh type numeric, char, dan bool sekaligus dengan custom struct dan enum. Sementara reference type mencakup semua class, array, delegate, dan interface types (termasuk string).

Perbedaan utama dari keduanya adalah terkait assignment dan passing data. Value type mengkopi data, menjadikan dua variabel benar-benar independen. Sedangakan reference type mengkopi reference (address), dua variabel mengandung reference yang meruju pada object yang memiliki value tersebut.

Value types

Isi dari variabel atau constant dari value type hanyalah sebuah nilai. Contohnya adalah isi dari built in value type, yaitu int adalah data 32 bits. Jika value type memiliki reference type sebagai data member maka yang dikopi adalah reference dari instance reference type tersebut. Jadi, baik instance value-type kopian maupun asli memiliki akses ke instance tipe referensi yang sama.

using System;
using System.Collections.Generic;
public struct TaggedInteger
{
    public int Number;
    private List<string> tags;
    public TaggedInteger(int n)
    {
        Number = n;
        tags = new List<string>();
    }
    public void AddTag(string tag) => tags.Add(tag);
    public override string ToString() => $"{Number} [{string.Join(", ", tags)}]";
}
public class Program
{
    public static void Main()
    {
        var n1 = new TaggedInteger(0);
        n1.AddTag("A");
        Console.WriteLine(n1);  // output: 0 [A]
        var n2 = n1;
        n2.Number = 7;
        n2.AddTag("B");
        Console.WriteLine(n1);  // output: 0 [A, B]
        Console.WriteLine(n2);  // output: 7 [A, B]
    }
}

Jenis-jenis value type dan batasan tipe (type constraints)

Sebuah value type bisa berupa salah satu dari jenis berikut:

  • Structure type, yang mengenkapsulasi data dan fungsionalitas terkait.
  • Enumeration type, yang didefinisikan oleh sekumpulan konstanta bernama dan merepresentasikan sebuah pilihan atau kombinasi pilihan.
  • Union declaration, yang mendefinisikan sekumpulan tertutup (closed set) dari case types yang dapat direpresentasikan oleh sebuah value.

Nullable value type T? merepresentasikan semua nilai dari underlying value type-nya, yaitu T, ditambah sebuah nilai null tambahan. Kita tidak bisa memberikan (assign) null ke variabel bertipe value type, kecuali variabel tersebut adalah nullable value type.

Gunakan constraint struct untuk menentukan bahwa sebuah type parameter adalah non-nullable value type. Baik structure type maupun enumeration type memenuhi constraint struct ini. Gunakan System.Enum pada base class constraint (yang dikenal sebagai enum constraint) untuk menentukan bahwa sebuah type parameter adalah enumeration type.

Built-in value types

C# menyediakan built-in value types berikut, yang juga dikenal sebagai simple types:

  • Integral numeric types
  • Floating-point numeric types
  • bool, yang merepresentasikan nilai Boolean
  • char, yang merepresentasikan karakter Unicode UTF-16

Semua simple types merupakan struct types. Yang membedakannya dari struct types lain adalah bahwa simple types mengizinkan beberapa operasi tambahan berikut:

  • Kamu bisa menggunakan literal untuk memberikan nilai dari sebuah simple type. Sebagai contoh, 'A' adalah literal bertipe char, 2001 adalah literal bertipe int, dan 12.34m adalah literal bertipe decimal.
  • Kamu bisa mendeklarasikan konstanta dari simple types menggunakan keyword const. Sebagai contoh, kamu bisa mendefinisikan const decimal = 12.34m. Kamu tidak bisa mendeklarasikan konstanta dari struct types lainnya.
  • Constant expression, yang semua operand-nya merupakan konstanta dari simple types, akan dievaluasi pada saat compile time.

Value tuple adalah sebuah value type, tetapi bukan simple type.

Reference Types

Reference type memiliki mekanisme yang lebih kompleks dari value type. Reference type memiliki dua bagian, yaitu sebuah object dan reference ke object tersebut. Isi dari variabel atau constant dari reference type adalah sebuah reference ke sebuah object yang mengandung value. Oleh karena itu operasi pada satu variabel dapat memengaruhi variabel lain yang mereferensikan object yang sama.

Keyword-keyword berikut ini untuk mendeklarasikan reference types:

Built in reference types:

Ketika sebuah class (yang merupakan reference type) mendeklarasikan value type sebagai sebuah field, data mentah dari value type tersebut tertanam langsung di dalam blok memori yang dialokasikan untuk instans class tersebut di managed heap.

Tata Letak Memori di Heap

Ketika kita membuat instans class menggunakan kata kunci new, runtime akan mengalokasikan satu blok memori yang berurutan pada managed heap. Blok ini berisi:

  1. Object Header (indeks sync block, metadata runtime)
  2. Method Table Pointer (TypeHandle)
  3. Instance Fields (disimpan secara berurutan dan langsung di tempat)

C#

public class UserAccount
{
    public int Id;          // Value type (4 byte)
    public bool IsActive;   // Value type (1 byte + padding)
    public string Name;     // Reference type (pointer referensi, 4 atau 8 byte)
}

Ketika new UserAccount() dipanggil:

Memori Managed Heap (untuk instans UserAccount):
[ Sync Block Index | TypeHandle | Id (4 byte) | IsActive (1 byte) | Padding | Name (Pointer) ]
  • Field berupa value type (Id, IsActive) tidak menunjuk ke lokasi memori lain; nilai biner aslinya berada langsung di dalam alokasi memori objek tersebut di heap.
  • Field berupa reference type (Name) hanya menyimpan alamat memori (pointer) yang menunjuk ke objek lain di lokasi berbeda pada heap.

Tambahan

Reference types → selalu di heap (bagian ini dari artikel sudah benar)

Sebuah instance class selalu dialokasikan di heap. Ini pada dasarnya adalah sebuah aturan pasti, karena “reference type” secara semantik menyiratkan lifetime yang dapat dibagikan (shareable) dan tidak dapat diprediksi — yang justru merupakan alasan keberadaan heap + GC. Variabel yang menyimpan reference-nya mungkin berada di stack, tetapi object yang ditunjuknya berada di heap.

Value types → “biasanya di stack” (di sinilah letak kesalahan artikel tersebut)

Byte dari sebuah value type berada di mana pun container-nya berada. Itulah aturan yang sebenarnya:

Value type tersebut adalah…Byte-nya berada di…
Variabel lokal di dalam sebuah methodDi stack
Field di dalam sebuah instance classDi heap, secara inline di dalam object tersebut
Elemen dari sebuah arrayDi heap, secara inline di dalam array tersebut
Ditangkap (captured) oleh lambda/closureDi heap, di dalam closure class yang dibuat otomatis oleh compiler
Di-boxing (di-assign ke object)Di heap, di dalam box object yang baru dibuat
Field di dalam struct lainDi mana pun struct tersebut berada

Jadi, “value type = stack” hanya benar untuk tepat satu baris dari tabel tersebut. Itu adalah kasus paling umum bagi pemula, dan itulah sebabnya mitos ini menyebar luas.

Stack vs heap, hubungan yang sebenarnya

  • Stack/heap berkaitan dengan manajemen lifetime, bukan identitas tipe. Stack = lifetime yang terikat pada satu call frame (dibersihkan secara otomatis). Heap = lifetime yang tidak dapat diprediksi (dikelola oleh GC).
  • Reference types: object-nya selalu berada di heap (lifetime-nya secara inheren tidak dapat diprediksi/dapat dibagikan).
  • Value types: berada di mana pun container-nya berada, di stack untuk variabel lokal, tetapi di heap ketika ia menjadi field sebuah class, elemen array, hasil capture closure, atau di-boxing.
  • Berada di heap tidak membuat sesuatu menjadi reference type. Copy semantics-nya tetap berperilaku seperti struct, di mana pun byte-nya secara fisik berada, dibuktikan lewat contoh Wrapper/Point, di mana memutasi hasil ekstraksi (copy) tidak memengaruhi data aslinya.
  • Stack dan heap adalah region memori fisik yang nyata (bukan sekadar istilah teknis), stack adalah memori yang benar-benar dikelola oleh OS/hardware untuk setiap thread; heap adalah struktur yang dikelola oleh CLR, di-garbage-collect, dan dapat dipadatkan (compacting). Boxing adalah bukti nyata dari biaya (cost) yang timbul saat berpindah antara keduanya.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *