Bagikan melalui


Jenis struktur (referensi C#)

Jenis strukturadalah jenis nilai yang dapat merangkum data dan fungsionalitas terkait. Anda menggunakan kata kunci struct untuk menentukan jenis struktur:

public struct Coords
{
    public Coords(double x, double y)
    {
        X = x;
        Y = y;
    }

    public double X { get; }
    public double Y { get; }

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

Untuk informasi tentang jenis ref struct dan readonly ref struct, lihat artikel jenis struktur ref.

Jenis struktur memiliki semantik nilai. Artinya, variabel dari jenis struktur berisi satu contoh dari jenis tersebut. Secara default, nilai variabel disalin pada penugasan, meneruskan argumen ke metode, dan mengembalikan hasil metode. Untuk variabel berjenis struktur, instans dari jenis tersebut akan disalin. Untuk informasi selengkapnya, lihat Jenis nilai.

Biasanya, Anda menggunakan jenis struktur untuk mendesain jenis data-sentris kecil yang memberikan perilaku yang hanya sedikit atau tidak sama sekali. Misalnya, .NET menggunakan jenis struktur untuk menunjukkan angka (baik bilangan bulat maupun riil), nilai Boolean, karakter Unicode, instans waktu. Jika Anda berfokus pada perilaku jenis, pertimbangkan untuk menentukan kelas. Jenis kelas memiliki semantik referensi. Artinya, variabel jenis kelas berisi referensi untuk instans jenis, bukan instans itu sendiri.

Karena jenis struktur memiliki semantik nilai, kami sarankan Anda menentukan jenis struktur yang tidak dapat diubah .

readonly struktur

Anda menggunakan pengubah readonly untuk menyatakan bahwa jenis struktur tidak dapat diubah. Semua anggota data dari struktur readonly harus bersifat baca-saja sebagai berikut:

  • Deklarasi bidang apa pun harus memiliki pengubah readonly.
  • Properti apa pun, termasuk yang diimplementasikan secara otomatis, harus bersifat hanya dapat dibaca atau init saja. Setter khusus init hanya tersedia dari C# versi 9 dan seterusnya.

Hal ini menjamin bahwa tidak ada anggota struktur readonly yang memodifikasi status struktur. Itu berarti bahwa anggota instans lainnya, kecuali konstruktor, adalah readonly implisit.

Catatan

Dalam struktur readonly, anggota data dari jenis referensi yang dapat diubah masih dapat mengubah statusnya sendiri. Misalnya, Anda tidak dapat mengganti instans List<T>, tetapi Anda dapat menambahkan elemen baru ke dalamnya.

Kode berikut mendefinisikan readonly struct dengan setter properti yang hanya dapat diinisialisasi:

public readonly struct Coords
{
    public Coords(double x, double y)
    {
        X = x;
        Y = y;
    }

    public double X { get; init; }
    public double Y { get; init; }

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

Anggota instans readonly

Anda juga dapat menggunakan pengubah readonly untuk menyatakan bahwa anggota instans tidak mengubah status struct. Jika Anda tidak dapat mendeklarasikan seluruh jenis struktur sebagai readonly, gunakan pengubah readonly untuk menandai anggota instans yang tidak mengubah status struktur.

Dalam suatu anggota instans readonly, Anda tidak dapat menetapkan nilai ke bidang instans dari struktur tersebut. Namun, anggota readonly dapat memanggil anggota non-readonly. Dalam hal ini, kompilator membuat salinan instance struktur dan memanggil anggota non-readonly pada salinan tersebut. Akibatnya, instans struktur asli tidak diubah.

Biasanya, Anda menerapkan pengubah readonly ke jenis anggota instans berikut:

  • metode:

    public readonly double Sum()
    {
        return X + Y;
    }
    

    Anda juga dapat menerapkan pengubah readonly ke metode yang mengambil alih metode yang dideklarasikan dalam System.Object:

    public readonly override string ToString() => $"({X}, {Y})";
    
  • properti dan pengindeks:

    private int counter;
    public int Counter
    {
        readonly get => counter;
        set => counter = value;
    }
    

    Jika Anda perlu menerapkan pengubah readonly baik ke pengakses properti maupun pengindeks, terapkan pengubah dalam deklarasi properti atau pengindeks.

    Catatan

    Kompilator mendeklarasikan get pengakses dari properti yang diimplementasikan secara otomatis sebagai readonly, terlepas dari keberadaan pengubah readonly dalam deklarasi properti.

    Anda dapat menerapkan pengubah readonly ke properti atau pengindeks dengan pengakses init:

    public readonly double X { get; init; }
    

Anda dapat menerapkan pengubah readonly ke bidang statis jenis struktur, tetapi bukan anggota statis lainnya, seperti properti atau metode.

Pengkompilasi dapat menggunakan pengubah readonly untuk pengoptimalan performa. Untuk informasi selengkapnya, lihat Menghindari alokasi.

Mutasi tidak merusak

Anda dapat menggunakan ekspresi with untuk menghasilkan salinan instans jenis struktur dengan properti dan bidang yang ditentukan yang dimodifikasi. Gunakan sintaks penginisialisasi objek untuk menentukan anggota yang akan diubah dan nilai barunya, seperti yang ditunjukkan oleh contoh berikut:

public readonly struct Coords
{
    public Coords(double x, double y)
    {
        X = x;
        Y = y;
    }

    public double X { get; init; }
    public double Y { get; init; }

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

public static void Main()
{
    var p1 = new Coords(0, 0);
    Console.WriteLine(p1);  // output: (0, 0)

    var p2 = p1 with { X = 3 };
    Console.WriteLine(p2);  // output: (3, 0)

    var p3 = p1 with { X = 1, Y = 4 };
    Console.WriteLine(p3);  // output: (1, 4)
}

record struktur

Anda dapat menentukan jenis struktur rekaman. Jenis rekaman menyediakan fungsionalitas bawaan untuk merangkum data. Anda dapat menentukan baik jenis record struct maupun readonly record struct. Struktur rekaman tidak boleh berupa ref struct. Untuk informasi dan contoh selengkapnya, lihat Rekaman.

Array dalam baris

Dimulai dengan C# 12, Anda dapat mendeklarasikan array inline sebagai tipe struct.

[System.Runtime.CompilerServices.InlineArray(10)]
public struct CharBuffer
{
    private char _firstElement;
}

Array inline adalah struktur yang berisi blok bersebelahan dari N elemen yang sejenis. Ini adalah setara kode aman untuk deklarasi buffer tetap yang hanya tersedia dalam kode tidak aman. Array sebaris adalah struct dengan karakteristik berikut:

  • Ini berisi satu bidang.
  • Struktur tidak menentukan tata letak eksplisit.

Selain itu, pengkompilasi memvalidasi System.Runtime.CompilerServices.InlineArrayAttribute atribut:

  • Panjangnya harus lebih besar dari nol (> 0).
  • Jenis target harus berupa struct.

Dalam kebanyakan kasus, array sebaris dapat diakses seperti array, baik untuk membaca dan menulis nilai. Selain itu, Anda dapat menggunakan operator rentang dan indeks .

Ada restriksi minimal pada jenis elemen tunggal dari array sebaris. Ini tidak bisa menjadi jenis penunjuk:

[System.Runtime.CompilerServices.InlineArray(10)]
public struct CharBufferWithPointer
{
    private unsafe char* _pointerElement;    // CS9184
}

Tetapi dapat berupa jenis referensi apa pun, atau jenis nilai apa pun:

[System.Runtime.CompilerServices.InlineArray(10)]
public struct CharBufferWithReferenceType
{
    private string _referenceElement;
}

Anda dapat menggunakan array sebaris dengan hampir semua struktur data C#.

Array inline adalah fitur bahasa tingkat lanjut. Elemen ini ditujukan untuk skenario performa tinggi di mana blok elemen yang sebaris dan berdampingan lebih cepat daripada struktur data alternatif lainnya. Anda dapat mempelajari selengkapnya tentang array sebaris dari spesifikasi fitur.

Inisialisasi struktur data dan nilai default

Variabel jenis struct secara langsung menampung data untuk struct tersebut. Hal ini akan membuat perbedaan antara struct yang tidak terinisialisasi, yang memiliki nilai default-nya dan struct yang diinisialisasi, yang menyimpan nilai yang ditetapkan dengan membuatnya. Misalnya, pertimbangkan kode berikut:

public readonly struct Measurement
{
    public Measurement()
    {
        Value = double.NaN;
        Description = "Undefined";
    }

    public Measurement(double value, string description)
    {
        Value = value;
        Description = description;
    }

    public double Value { get; init; }
    public string Description { get; init; }

    public override string ToString() => $"{Value} ({Description})";
}

public static void Main()
{
    var m1 = new Measurement();
    Console.WriteLine(m1);  // output: NaN (Undefined)

    var m2 = default(Measurement);
    Console.WriteLine(m2);  // output: 0 ()

    var ms = new Measurement[2];
    Console.WriteLine(string.Join(", ", ms));  // output: 0 (), 0 ()
}

Seperti yang ditunjukkan contoh sebelumnya, ekspresi nilai default mengabaikan konstruktor tanpa parameter dan menghasilkan nilai default jenis struktur. Instansiasi array berjenis struktur juga mengabaikan konstruktor tanpa parameter dan menghasilkan array yang berisikan nilai default jenis struktur.

Situasi paling umum di mana Anda melihat nilai default adalah dalam array atau di koleksi lain di mana penyimpanan internal menyertakan blok variabel. Contoh berikut membuat array 30 struktur TemperatureRange, yang masing-masing memiliki nilai default:

// All elements have default values of 0:
TemperatureRange[] lastMonth = new TemperatureRange[30];

Semua bidang anggota struct harus pasti ditetapkan saat dibuat karena jenis struct menyimpan data mereka secara langsung. Nilai default dari sebuah struct menetapkan semua bidang ke 0. Semua variabel harus benar-benar ditetapkan saat konstruktor dipanggil. Anda menginisialisasi bidang menggunakan mekanisme berikut:

  • Anda dapat menambahkan penginisialisasi bidang ke bidang apa pun atau properti yang diimplementasikan secara otomatis.
  • Anda dapat menginisialisasi bidang mana pun, atau properti otomatis, di dalam isi konstruktor.

Jika Anda tidak menginisialisasi semua bidang dalam struct, pengkompilasi menambahkan kode ke konstruktor yang menginisialisasi bidang tersebut ke nilai default. Struktur yang ditetapkan ke nilai default diinisialisasi ke pola bit 0. Struktur yang diinisialisasi dengan new diinisialisasi ke pola 0-bit, diikuti dengan menjalankan inisialisasi bidang dan konstruktor.

public readonly struct Measurement
{
    public Measurement(double value)
    {
        Value = value;
    }

    public Measurement(double value, string description)
    {
        Value = value;
        Description = description;
    }

    public Measurement(string description)
    {
        Description = description;
    }

    public double Value { get; init; }
    public string Description { get; init; } = "Ordinary measurement";

    public override string ToString() => $"{Value} ({Description})";
}

public static void Main()
{
    var m1 = new Measurement(5);
    Console.WriteLine(m1);  // output: 5 (Ordinary measurement)

    var m2 = new Measurement();
    Console.WriteLine(m2);  // output: 0 ()

    var m3 = default(Measurement);
    Console.WriteLine(m3);  // output: 0 ()
}

Setiap struct memiliki konstruktor tanpa parameter public. Jika Anda menulis konstruktor tanpa parameter, maka konstruktor tersebut harus yang publik. Jika struktur mendeklarasikan penginisialisasi bidang apa pun, struct harus secara eksplisit mendeklarasikan konstruktor. Konstruktor tersebut tidak harus tanpa parameter. Jika struct mendeklarasikan penginisialisasi bidang tetapi tidak ada konstruktor, kompilator melaporkan kesalahan. Setiap konstruktor yang dideklarasikan secara eksplisit (dengan parameter, atau tanpa parameter) menjalankan semua penginisialisasi bidang untuk struktur tersebut. Semua bidang yang tidak memiliki penginisialisasi atau pengaturan dalam konstruktor diatur ke nilai default. Untuk informasi selengkapnya, lihat catatan proposal fitur konstruktor struktur tanpa parameter.

Dimulai dengan C# 12, struct jenis dapat menentukan konstruktor utama sebagai bagian dari deklarasinya. Konstruktor utama menyediakan sintaks ringkas untuk parameter konstruktor yang dapat digunakan di seluruh isi struct, dalam deklarasi anggota apa pun untuk struktur tersebut.

Jika semua bidang instans dari tipe struktur dapat diakses, Anda juga dapat menginstansiasi tanpa operator new. Dalam hal ini Anda harus menginisialisasi semua bidang instans sebelum penggunaan instans yang pertama kalinya. Contoh berikut ini menunjukkan cara melakukannya:

public static class StructWithoutNew
{
    public struct Coords
    {
        public double x;
        public double y;
    }

    public static void Main()
    {
        Coords p;
        p.x = 3;
        p.y = 4;
        Console.WriteLine($"({p.x}, {p.y})");  // output: (3, 4)
    }
}

Untuk jenis nilai bawaan , gunakan literal yang sesuai untuk menentukan nilai jenisnya.

Batasan dengan desain jenis struktur

Struktur memiliki sebagian besar kapabilitas dari jenis kelas. Ada beberapa pengecualian:

  • Jenis struktur tidak dapat mewarisi dari kelas atau jenis struktur lain dan tidak dapat menjadi dasar kelas. Namun, jenis struktur dapat mengimplementasikan antarmuka.
  • Anda tidak dapat mendeklarasikan finalizer dalam jenis struktur.
  • Konstruktor jenis struktur harus menginisialisasi semua bidang instans jenis.

Meneruskan variabel bertipe struktur melalui referensi

Saat Anda meneruskan variabel berjenis struktur ke metode sebagai argumen atau mengembalikan nilai jenis struktur dari metode, seluruh instans jenis struktur akan disalin. Meneruskan nilai dapat memengaruhi kinerja kode Anda dalam skenario berkinerja tinggi yang melibatkan tipe struktur besar. Anda dapat menghindari penyalinan nilai dengan meneruskan variabel berjenis struktur dengan referensi. refGunakan pengubah parameter metode , out, in, atau ref readonly untuk menunjukkan bahwa argumen harus diteruskan oleh referensi. Gunakan pengembalian ref untuk mengembalikan hasil metode berdasarkan referensi. Untuk informasi selengkapnya, lihat Cara Menghindari Alokasi.

batasan struct

Anda juga menggunakan kata kunci struct dalam batasan struct untuk menentukan bahwa parameter jenis adalah jenis nilai yang tidak dapat diubah ke null. Jenis struktur dan enumerasi memenuhi batasan struct.

Konversi

Untuk tipe struktur apa pun (kecuali tipe ref struct), ada konversi pembungkusan dan pembongkaran ke dan dari tipe dan System.ValueType. Terdapat juga konversi boxing dan unboxing antara jenis struktur dan antarmuka apa pun yang diimplementasikannya.

Spesifikasi bahasa C#

Untuk informasi selengkapnya, lihat bagian Struktur dari Spesifikasi bahasa C#.

Untuk informasi selengkapnya tentang fitur struct, lihat catatan fitur berikut:

Lihat juga