struct

Tipe nilai kustom, struct readonly, dan kapan struct dipilih.

Daftar isi

struct adalah tipe nilai yang didefinisikan sendiri untuk mengelompokkan beberapa data kecil menjadi satu kesatuan, misalnya titik koordinat, suhu, atau ukuran. Topik ini membahas cara mendeklarasikan struct beserta field dan metodenya, bentuk readonly struct yang menolak perubahan, serta pertimbangan ketika struct dipilih alih-alih kelas. Semua contoh dijalankan langsung, sehingga semantik penyalinan yang menjadi ciri struct dapat diamati dari keluarannya.

Mendeklarasikan struct#

Struct dideklarasikan dengan kata kunci struct, diikuti field dan metode yang menyusunnya. Struct Titik pada program berikut memuat dua field int bernama X dan Y, satu metode Geser yang mengubah kedua field tersebut, serta penulisan ulang metode ToString agar pencetakan terbaca. Sebuah struct dibuat dengan new beserta penginisialisasi object, dan karena termasuk tipe nilai, setiap penugasan menyalin seluruh isinya. Struct yang tidak pernah diisi pun tetap memiliki nilai, yaitu nilai default setiap fieldnya, dan kata kunci default menghasilkan struct kosong tersebut secara eksplisit.

csharp Program.cs
// Program demonstrasi struct dan semantik penyalinannya
Titik p1 = new Titik { X = 2, Y = 3 };
Titik p2 = p1;              // penugasan menyalin seluruh nilai struct
p2.Geser(10, 5);

Console.WriteLine($"p1 = {p1}");
Console.WriteLine($"p2 = {p2}");
Console.WriteLine($"p1.Equals(p2) -> {p1.Equals(p2)}");

Titik p3 = default;         // struct selalu memiliki nilai default
Console.WriteLine($"p3 = {p3}");

Suhu suhuRuang = new Suhu(24.5);
Console.WriteLine($"suhuRuang = {suhuRuang}");

struct Titik
{
    public int X;
    public int Y;

    public void Geser(int dx, int dy)
    {
        X += dx;
        Y += dy;
    }

    public override string ToString() => $"({X}, {Y})";
}

readonly struct Suhu
{
    public double Derajat { get; }

    public Suhu(double derajat)
    {
        Derajat = derajat;
    }

    public override string ToString() => $"{Derajat} derajat";
}
text Keluaran
p1 = (2, 3)
p2 = (12, 8)
p1.Equals(p2) -> False
p3 = (0, 0)
suhuRuang = 24.5 derajat

Untuk menjalankan program tersebut dari folder topik, gunakan perintah berikut.

bash
# aktifkan .NET yang terpasang di sandbox
export DOTNET_ROOT=/home/kal/.dotnet PATH=/home/kal/.dotnet:$PATH DOTNET_CLI_TELEMETRY_OPTOUT=1 DOTNET_NOLOGO=1
# jalankan program contoh dari folder topik
dotnet run --file Program.cs
text Keluaran
p1 = (2, 3)
p2 = (12, 8)
p1.Equals(p2) -> False
p3 = (0, 0)
suhuRuang = 24.5 derajat

Keluaran menegaskan sifat tipe nilai pada struct. Variabel p2 ditugaskan dari p1, lalu digeser dengan Geser, dan hasilnya p2 berpindah ke (12, 8) sedangkan p1 tetap di (2, 3). Metode Geser bekerja pada salinan yang dipegang p2, bukan pada data yang dipegang p1, karena penugasan menyalin seluruh struct. Metode Equals pada struct membandingkan isi field-nya, sehingga mengembalikan False ketika kedua titik sudah tidak sama, dan berbeda dengan kelas yang secara default membandingkan identitas object.

struct readonly#

readonly struct adalah struct yang seluruh anggota instansinya bersifat tidak dapat diubah (immutable), yaitu datanya tetap setelah dibuat. Deklarasinya memakai kata kunci readonly di depan struct, dan kompilator (compiler) menegakkan aturannya dengan menolak field maupun properti yang dapat ditulisi. Struct Suhu pada program di atas memakai pola ini, yaitu properti Derajat hanya diisi melalui konstruktor dan tidak ada metode yang mengubahnya. Desain semacam ini membuat struct aman disalin ke segala arah karena tidak ada salinan yang berbeda dari asalnya.

Kompilator mengawal keputusan ini sampai ke detail terkecil. Field di dalam struct readonly wajib dideklarasikan readonly, dan pelanggarannya ditolak sejak tahap kompilasi.

csharp Contoh: field struct readonly wajib dideklarasikan readonly
// tidak-dijalankan
// Contoh: field struct readonly wajib dideklarasikan readonly
readonly struct Suhu
{
    public double Derajat;
}

Kompilator melaporkan error CS8340, yaitu pesan Instance fields of readonly structs must be readonly. Aturan ini mencegah struct readonly berisi data yang bisa berubah, sehingga status tidak dapat diubah benar-benar terjamin dan bukan sekadar kesepakatan di antara penulis kode. Struct yang memang perlu berubah, seperti Titik dengan metode Geser, sebaiknya tidak dideklarasikan readonly, atau lebih baik lagi dipertimbangkan menjadi kelas karena semantik penyalinannya menyesatkan.

Kapan struct dipilih#

Pemilihan antara struct dan kelas mengikuti bentuk datanya serta cara datanya dipakai. Struct tepat untuk data kecil yang isinya jelas dan jarang berubah, sedangkan kelas tepat untuk object yang besar, berubah, atau perlu saling terhubung melalui pewarisan. Tabel berikut merangkum perbandingannya.

AspekstructKelas
Kategori tipetipe nilaitipe referensi
Penugasanmenyalin seluruh isimenyalin reference
Penyimpananmengikuti konteks, umumnya stackobject di heap
Pewarisantidak dapat mewarisi, dapat mengimplementasikan antarmuka (interface)dapat mewarisi dan diwarisi
Kesetaraan default Equalsmembandingkan isimembandingkan identitas

Panduan praktisnya dapat dirangkum dalam tiga pertanyaan. Pertama, apakah datanya kecil dan terdiri atas beberapa field angka atau teks pendek, karena struct besar menimbulkan biaya penyalinan pada setiap penugasan dan pengiriman parameter. Kedua, apakah nilainya tidak berubah setelah dibuat, karena struct yang sering diubah mudah menimbulkan kebingungan antara asli dan salinannya. Ketiga, apakah instansnya dibuat dan dibuang dalam jumlah besar, karena struct tidak menambah beban garbage collector, bagian runtime yang mengosongkan memori object yang tidak dirujuk lagi. Titik koordinat, suhu, warna, dan interval singkat adalah contoh yang cocok, sedangkan data transaksi dan dokumen lebih cocok menjadi kelas.

Kesalahan umum#

  • Memilih struct untuk data besar yang sering ditugaskan. Setiap penugasan menyalin seluruh isinya, sehingga biayanya tumbuh seiring ukuran struct dan seringnya penyalinan, dan kelas menjadi pilihan yang lebih hemat.
  • Mengandalkan perubahan melalui salinan. Memanggil metode pengubah pada variabel salin hanya mengubah salinannya, sehingga struct yang memang perlu berubah sebaiknya menjadi kelas.
  • Menganggap variabel struct bisa bernilai null. Struct adalah tipe nilai yang selalu memegang sesuatu, dan default menghasilkan struct berisi nol, bukan reference kosong.

Ringkasan#

  • struct adalah tipe nilai kustom yang disalin utuh pada setiap penugasan, sehingga perubahan pada salinan tidak pernah menyentuh asalnya.
  • readonly struct menjamin isinya tidak berubah setelah dibuat, dan kompilator menolak field serta propertinya yang dapat ditulisi dengan error CS8340.
  • struct dipilih untuk data kecil, tidak berubah, dan dibuat dalam jumlah besar, sedangkan data besar, berubah, atau berpewarisan lebih cocok menjadi kelas.

Referensi#