Rekaman (referensi C#)

Pengubah record menyediakan fungsionalitas bawaan untuk merangkum data. Sintaksis record class dan record menentukan jenis referensi. record struct Sintaks mendefinisikan jenis nilai.

Referensi bahasa C# mendanai versi bahasa C# yang terbaru dirilis. Ini juga berisi dokumentasi awal untuk fitur dalam pratinjau publik untuk rilis bahasa yang akan datang.

Dokumentasi mengidentifikasi fitur apa pun yang pertama kali diperkenalkan dalam tiga versi terakhir bahasa atau dalam pratinjau publik saat ini.

Tip

Untuk menemukan kapan fitur pertama kali diperkenalkan di C#, lihat artikel tentang riwayat versi bahasa C#.

Saat Anda mendeklarasikan konstruktor utama pada rekaman, pengkompilasi menghasilkan properti publik untuk parameter konstruktor utama. Parameter konstruktor utama ke rekaman adalah parameter posisional. Pengkompilasi membuat properti posisional yang mencerminkan parameter konstruktor utama atau posisi. Pengkompilasi tidak mensintesis properti untuk parameter konstruktor utama pada jenis yang tidak memiliki record pengubah.

Dua contoh berikut menunjukkan jenis referensi record (atau record class):

public record Person(string FirstName, string LastName);
public record Person
{
    public required string FirstName { get; init; }
    public required string LastName { get; init; }
};

Dua contoh berikut menunjukkan jenis nilai record struct:

public readonly record struct Point(double X, double Y, double Z);
public record struct Point
{
    public double X { get; init; }
    public double Y { get; init; }
    public double Z { get; init; }
}

Anda juga bisa membuat rekaman dengan properti dan bidang yang dapat diubah:

public record Person
{
    public required string FirstName { get; set; }
    public required string LastName { get; set; }
};

Record structs juga dapat berubah, baik record structs posisional maupun record structs tanpa parameter posisional:

public record struct DataMeasurement(DateTime TakenAt, double Measurement);
public record struct Point
{
    public double X { get; set; }
    public double Y { get; set; }
    public double Z { get; set; }
}

Meskipun rekaman dapat berubah, rekaman ini terutama ditujukan untuk mendukung model data yang tidak berubah. Jenis rekaman tersebut menawarkan fitur-fitur berikut:

Contoh sebelumnya memperlihatkan beberapa perbedaan antara rekaman yang merupakan jenis referensi dan rekaman yang merupakan jenis nilai:

  • record atau record class menyatakan jenis referensi. Kata kunci class bersifat opsional, tetapi dapat menambahkan kejelasan bagi pembaca. record struct mendeklarasikan jenis nilai.
  • Properti posisi tidak dapat diubah dalam record class dan readonly record struct. Mereka dapat diubah dalam record struct.

Sisa artikel ini membahas jenis record class dan record struct. Perbedaannya dirinci di setiap bagian. Memutuskan antara record class dan yang record struct mirip dengan memutuskan antara dan classstruct. Istilah catatan menjelaskan perilaku yang berlaku untuk semua jenis rekaman. Baik record struct atau record class digunakan untuk menggambarkan perilaku yang hanya berlaku untuk jenis struct atau kelas.

Sintaks posisi untuk definisi properti dan bidang

Gunakan parameter posisi untuk mendeklarasikan properti rekaman atau untuk menginisialisasi nilai properti atau bidang. Contoh berikut membuat rekaman dengan dua properti posisi:

public record Person(string FirstName, string LastName);

public static void Main()
{
    Person person = new("Nancy", "Davolio");
    Console.WriteLine(person);
    // output: Person { FirstName = Nancy, LastName = Davolio }
}

Saat Anda menggunakan sintaksis posisi untuk definisi properti, pengompilasi akan membuat:

  • Properti publik yang diimplementasikan secara otomatis untuk setiap parameter posisi yang disediakan dalam deklarasi rekaman.
    • Untuk jenis record dan jenis readonly record struct: Properti khusus init.
    • Untuk jenis record struct: Properti baca-tulis.
  • Konstruktor utama yang parameternya cocok dengan parameter posisi pada deklarasi rekaman.
  • Untuk jenis struct rekaman, konstruktor tanpa parameter yang menetapkan setiap bidang ke nilai defaultnya.
  • Metode Deconstruct dengan parameter out untuk setiap parameter posisi disediakan dalam deklarasi rekaman. Metode ini mendekonstruksi properti yang ditentukan dengan menggunakan sintaks posisional; itu mengabaikan properti yang didefinisikan dengan menggunakan sintaks properti standar.

Anda mungkin ingin menambahkan atribut ke salah satu elemen ini yang dibuat pengkompilasi dari definisi rekaman. Anda dapat menambahkan target ke atribut apa pun yang Anda terapkan ke properti rekaman posisi. Contoh berikut menerapkan ke System.Text.Json.Serialization.JsonPropertyNameAttribute setiap properti rekaman Person. Target property: menunjukkan bahwa atribut diterapkan ke properti yang dihasilkan kompiler. Nilai lain adalah field: untuk menerapkan atribut ke bidang, dan param: untuk menerapkan atribut ke parameter.

/// <summary>
/// Person record type
/// </summary>
/// <param name="FirstName">First Name</param>
/// <param name="LastName">Last Name</param>
/// <remarks>
/// The person type is a positional record containing the
/// properties for the first and last name. Those properties
/// map to the JSON elements "firstName" and "lastName" when
/// serialized or deserialized.
/// </remarks>
public record Person([property: JsonPropertyName("firstName")] string FirstName,
    [property: JsonPropertyName("lastName")] string LastName);

Contoh sebelumnya juga menunjukkan cara membuat komentar dokumentasi XML untuk rekaman. Anda dapat menambahkan tag <param> untuk menambahkan dokumentasi bagi parameter konstruktor utama.

Jika definisi properti yang diimplementasikan secara otomatis tidak seperti yang Anda inginkan, tentukan properti atau bidang Anda sendiri dengan nama yang sama. Misalnya, Anda mungkin ingin mengubah aksesibilitas atau mutabilitas, atau memberikan implementasi untuk pengakses get atau set. Jika Anda mendeklarasikan anggota di sumber Anda, Anda harus menginisialisasinya dari parameter posisi rekaman. Jika properti Anda adalah properti yang diimplementasikan secara otomatis, Anda harus menginisialisasi properti . Jika Anda menambahkan bidang pendukung di sumber, Anda harus menginisialisasi bidang pendukung. Dekonstruktor yang dihasilkan menggunakan definisi properti atau bidang Anda. Misalnya, contoh berikut mendeklarasikan properti FirstName dan LastName dari rekaman posisi public, tetapi membatasi parameter posisi Id ke internal. Anda dapat menggunakan sintaks ini untuk jenis rekaman dan struct rekaman.

public record Person(string FirstName, string LastName, string Id)
{
    internal string Id { get; init; } = Id;
}

public static void Main()
{
    Person person = new("Nancy", "Davolio", "12345");
    Console.WriteLine(person.FirstName); //output: Nancy

}

Jika Anda ingin membuat bidang alih-alih properti, tetapkan parameter posisi ke bidang, seperti yang diperlihatkan dalam contoh berikut:

public record Person(string FirstName, string LastName, string Id)
{
    internal readonly string Id = Id; // this.Id set to parameter Id
}

public static void Main()
{
    Person person = new("Nancy", "Davolio", "12345");
    Console.WriteLine(person.FirstName); //output: Nancy

}

Jenis rekaman tidak harus mendeklarasikan properti posisi apa pun. Anda dapat mendeklarasikan rekaman tanpa properti posisi apa pun, dan Anda dapat mendeklarasikan bidang dan properti lain, seperti dalam contoh berikut:

public record Person(string FirstName, string LastName)
{
    public string[] PhoneNumbers { get; init; } = [];
};

Properti yang dihasilkan kompilator dari parameter posisi public. Anda mendeklarasikan pengubah akses pada properti apa pun yang anda nyatakan secara eksplisit.

Ketetapan

kelas rekaman posisional dan struktur rekaman posisi mendeklarasikan properti khusus init. Struct rekaman posisional mendeklarasikan properti baca-tulis. Anda dapat menimpa salah satu default tersebut, seperti yang ditunjukkan di bagian sebelumnya.

Kekekalan mungkin berguna ketika Anda memerlukan jenis yang berentrik data agar aman untuk utas atau ketika Anda bergantung pada kode hash yang tetap sama dalam tabel hash. Namun, kekekalan tidak sesuai untuk semua skenario data. Entity Framework Core, misalnya, tidak mendukung pembaruan dengan jenis entitas yang tidak dapat diubah.

Properti khusus init, baik yang dibuat dari parameter posisional (record class dan readonly record struct) atau dengan menentukan init pengakses, memiliki kekekalan yang dangkal. Setelah inisialisasi, Anda tidak dapat mengubah nilai properti jenis nilai atau referensi dari properti jenis referensi. Namun, data yang dirujuk properti jenis referensi dapat diubah. Contoh berikut menunjukkan bahwa konten properti tidak berubah tipe referensi (array dalam hal ini) dapat berubah:

public record Person(string FirstName, string LastName, string[] PhoneNumbers);

public static void Main()
{
    Person person = new("Nancy", "Davolio", new string[1] { "555-1234" });
    Console.WriteLine(person.PhoneNumbers[0]); // output: 555-1234

    person.PhoneNumbers[0] = "555-6789";
    Console.WriteLine(person.PhoneNumbers[0]); // output: 555-6789
}

Fitur yang unik untuk jenis rekaman diimplementasikan oleh metode yang disintesis kompiler, dan tidak ada satu pun dari metode tersebut yang berkompromi terhadap ketetapan dengan memodifikasi status objek. Kecuali ditentukan, metode yang disintesis dihasilkan untuk deklarasi record, record struct, dan readonly record struct.

Kesetaraan nilai

Jika Anda tidak mengambil alih atau mengganti metode kesetaraan, jenis yang Anda nyatakan mengatur bagaimana kesetaraan ditentukan:

  • Untuk jenis class, dua objek sama jika merujuk ke objek yang sama dalam memori.
  • Untuk jenis struct, dua objek sama jika memiliki jenis yang sama dan menyimpan nilai yang sama.
  • Untuk jenis dengan pengubah record (record class, record struct, dan readonly record struct), dua objek sama jika memiliki jenis yang sama dan menyimpan nilai yang sama.

Definisi kesetaraan untuk record struct sama dengan untuk struct. Perbedaannya adalah bahwa untuk struct, implementasi berada di ValueType.Equals(Object) dan bergantung pada refleksi. Untuk rekaman, implementasinya disintesis kompiler dan menggunakan anggota data yang dideklarasikan.

Kesetaraan referensi diperlukan untuk beberapa model data. Misalnya, Entity Framework Core tergantung pada kesetaraan referensi untuk memastikan bahwa ia hanya menggunakan satu instans jenis entitas untuk apa yang secara konseptual satu entitas. Untuk alasan ini, rekaman dan struktur rekaman tidak sesuai untuk digunakan sebagai jenis entitas dalam Entity Framework Core.

Contoh berikut mengilustrasikan kesetaraan nilai jenis rekaman:

public record Person(string FirstName, string LastName, string[] PhoneNumbers);

public static void Main()
{
    var phoneNumbers = new string[2];
    Person person1 = new("Nancy", "Davolio", phoneNumbers);
    Person person2 = new("Nancy", "Davolio", phoneNumbers);
    Console.WriteLine(person1 == person2); // output: True

    person1.PhoneNumbers[0] = "555-1234";
    Console.WriteLine(person1 == person2); // output: True

    Console.WriteLine(ReferenceEquals(person1, person2)); // output: False
}

Untuk mengimplementasikan kesetaraan nilai, kompilator mensintesis beberapa metode, termasuk:

  • Penimpaan dari Object.Equals(Object). Ini adalah kesalahan jika Anda menyatakan penimpaan secara eksplisit.

    Metode ini digunakan sebagai dasar untuk metode statis Object.Equals(Object, Object) ketika kedua parameter non-null.

  • , virtualatau sealed, Equals(R? other) di mana R adalah jenis catatan. Metode ini mengimplementasikan IEquatable<T>. Anda dapat mendeklarasikan metode ini secara eksplisit.

  • Jika jenis catatan berasal dari jenis Basecatatan dasar , Equals(Base? other). Ini adalah kesalahan jika Anda menyatakan penimpaan secara eksplisit. Jika Anda memberikan implementasi Anda sendiri dari Equals(R? other), berikan implementasi GetHashCode juga.

  • Penimpaan dari Object.GetHashCode(). Anda dapat mendeklarasikan metode ini secara eksplisit.

  • Mengambil alih operator == dan operator !=. Ini adalah kesalahan jika Anda mendeklarasikan operator secara eksplisit.

  • Jika jenis rekaman berasal dari jenis catatan dasar, protected override Type EqualityContract { get; };. Anda dapat mendeklarasikan properti ini secara eksplisit. Untuk informasi selengkapnya, lihat Kesetaraan dalam hierarki pewarisan.

Kompilator tidak mensintesis metode ketika jenis rekaman memiliki metode yang cocok dengan tanda tangan metode yang disintesis dan metode diizinkan untuk dinyatakan secara eksplisit.

Mutasi tidak merusak

Jika Anda perlu menyalin instans dengan beberapa modifikasi, gunakan with ekspresi untuk mencapai mutasi yang tidak merusak. with Ekspresi membuat instans rekaman baru yang merupakan salinan instans rekaman yang sudah ada, tetapi dengan properti dan bidang tertentu dimodifikasi. Gunakan sintaks penginisialisasi objek untuk menentukan nilai yang akan diubah, seperti yang ditunjukkan dalam contoh berikut:

public record Person(string FirstName, string LastName)
{
    public string[] PhoneNumbers { get; init; }
}

public static void Main()
{
    Person person1 = new("Nancy", "Davolio") { PhoneNumbers = new string[1] };
    Console.WriteLine(person1);
    // output: Person { FirstName = Nancy, LastName = Davolio, PhoneNumbers = System.String[] }

    Person person2 = person1 with { FirstName = "John" };
    Console.WriteLine(person2);
    // output: Person { FirstName = John, LastName = Davolio, PhoneNumbers = System.String[] }
    Console.WriteLine(person1 == person2); // output: False

    person2 = person1 with { PhoneNumbers = new string[1] };
    Console.WriteLine(person2);
    // output: Person { FirstName = Nancy, LastName = Davolio, PhoneNumbers = System.String[] }
    Console.WriteLine(person1 == person2); // output: False

    person2 = person1 with { };
    Console.WriteLine(person1 == person2); // output: True
}

Ekspresi with dapat mengatur properti posisi atau properti yang dibuat dengan menggunakan sintaks properti standar. Properti yang dideklarasikan secara eksplisit harus memiliki init aksesor atau set untuk diubah dalam with ekspresi.

Hasil ekspresi with adalah salinan dangkal. Untuk properti referensi, ekspresi hanya menyalin referensi ke instans. Baik rekaman asli maupun salinan berakhir dengan referensi ke instans yang sama.

Untuk mengimplementasikan fitur ini untuk jenis record class, kompiler mensintesis metode kloning dan konstruktor salinan. Metode kloning virtual menghasilkan rekaman baru yang diinisialisasi oleh konstruktor salinan. Saat Anda menggunakan with ekspresi, pengkompilasi membuat kode yang memanggil metode kloning lalu mengatur properti yang disertakan with ekspresi.

Penting

Kompilator juga mensintesis konstruktor tanpa parameter publik ketika catatan tidak memiliki konstruktor utama atau konstruktor yang ditentukan pengguna. Konstruktor tanpa parameter ini menginisialisasi semua bidang ke nilai defaultnya. Tanpa konstruktor yang disintesis ini, tidak ada konstruktor publik yang tersedia.

Jika Anda memerlukan perilaku penyalinan yang berbeda, tulis konstruktor salinan Anda sendiri di record class. Jika ya, pengkompilasi tidak mensintesisnya. Buat konstruktor private Anda jika rekamannya adalah sealed. Jika tidak, buatlah protected. Kompiler tidak mensintesis konstruktor salinan untuk jenisrecord struct. Anda dapat menulisnya, tetapi pengkompilasi tidak menghasilkan panggilan untuk with ekspresi. Nilai record struct disalin pada penugasan.

Anda tidak dapat menimpa metode kloning, dan Anda tidak dapat membuat anggota bernama Clone dalam jenis rekaman apa pun. Nama aktual dari metode kloning dihasilkan kompiler.

Penting

Dalam contoh sebelumnya, semua properti independen. Tidak ada properti yang dihitung dari nilai properti lainnya. Ekspresi with terlebih dahulu menyalin instans rekaman yang ada, lalu memodifikasi properti atau bidang apa pun yang disertakan ekspresi with . Properti komputasi dalam record jenis harus dihitung pada akses, bukan diinisialisasi saat instans dibuat. Jika tidak, properti dapat mengembalikan nilai komputasi berdasarkan instans asli, bukan salinan yang dimodifikasi.

Anda memastikan kebenaran pada properti komputasi dengan menghitung nilai pada akses, seperti yang ditunjukkan dalam deklarasi berikut:

public record Point(int X, int Y)
{
    public double Distance => Math.Sqrt(X * X + Y * Y);
}

Jenis catatan sebelumnya menghitung Distance saat diakses, seperti yang diperlihatkan dalam contoh berikut:

Point p1 = new Point(3, 4);
Console.WriteLine($"Original point: {p1}");
p1 = p1 with { Y = 8 };
Console.WriteLine($"Modified point: {p1}");
// Output:
// Original point: Point { X = 3, Y = 4, Distance = 5 }
// Modified point: Point { X = 3, Y = 8, Distance = 8.54400374531753 }

Kontras pendekatan tersebut dengan deklarasi berikut, di mana Distance properti dihitung dan di-cache sebagai bagian dari inisialisasi instans baru:

public record PointInit(int X, int Y)
{
    public double Distance { get; } = Math.Sqrt(X * X + Y * Y);
}

Karena Distance dihitung sebagai bagian dari inisialisasi, nilai dihitung dan di-cache sebelum with ekspresi mengubah nilai Y dalam salinan. Hasilnya adalah bahwa jarak salah:

PointInit pt1 = new PointInit(3, 4);
Console.WriteLine($"Original point: {pt1}");
pt1 = pt1 with { Y = 8 };
Console.WriteLine($"Incorrect Modified point: {pt1}");
// Output:
// Original point: PointInit { X = 3, Y = 4, Distance = 5 }
// Modified point: PointInit { X = 3, Y = 8, Distance = 5 }

Komputasi Distance tidak mahal untuk dihitung pada setiap akses. Namun, beberapa properti komputasi mungkin memerlukan akses ke lebih banyak data atau komputasi yang lebih luas. Dalam kasus tersebut, alih-alih rekaman, gunakan class jenis dan komputasi nilai cache saat salah satu komponen mengubah nilai.

Pemformatan bawaan untuk ditampilkan

Jenis rekaman memiliki metode ToString yang dihasilkan kompiler yang menampilkan nama dan nilai properti serta bidang publik. Metode ToString mengembalikan string dalam format berikut:

<nama jenis rekaman> { <property name> = <value>, <property name> = <value>, ...}

String yang dicetak untuk <value> adalah string yang dikembalikan oleh ToString() untuk jenis properti. Dalam contoh berikut, ChildNames adalah System.Array, di mana ToString mengembalikan System.String[]:

Person { FirstName = Nancy, LastName = Davolio, ChildNames = System.String[] }

Untuk mengimplementasikan fitur ini, dalam jenis record class, Kompiler mensintesis metode virtual PrintMembers dan menimpa ToString. Dalam jenis record struct, anggota ini adalah private. Penimpaan ToString membuat objek StringBuilder dengan nama jenis diikuti dengan tanda kurung buka. Ini memanggil PrintMembers untuk menambahkan nama dan nilai properti, lalu menambahkan tanda kurung tutup. Contoh berikut menunjukkan kode yang mirip dengan apa yang dikandung oleh penimpaan yang disintesis:

public override string ToString()
{
    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder.Append("Teacher"); // type name
    stringBuilder.Append(" { ");
    if (PrintMembers(stringBuilder))
    {
        stringBuilder.Append(" ");
    }
    stringBuilder.Append("}");
    return stringBuilder.ToString();
}

Anda dapat memberikan implementasi PrintMembers milik Anda sendiri atau penimpaan ToString. Contoh disediakan dalam pemformatan PrintMembers di bagian rekaman turunan nanti di artikel ini. Implementasi ToString Anda mungkin mencakup pengubah sealed, yang mencegah pengkompilasi mensintesis implementasi ToString untuk rekaman turunan apa pun. Anda dapat membuat representasi string yang konsisten di seluruh hierarki jenis record . (Rekaman turunan masih memiliki metode yang PrintMembers dihasilkan untuk semua properti turunan.)

Warisan

Bagian ini hanya berlaku pada jenis record class.

Rekaman dapat mewariskan dari rekaman lain. Namun, catatan tidak dapat menerima warisan dari kelas, dan kelas tidak dapat menerima warisan dari catatan.

Mulai dari C# 15, dapat record class menggunakan pengubah closed untuk membatasi derivasi langsung ke assembly yang mendeklarasikan. Konsumen kemudian dapat menulis ekspresi lengkap switch atas turunan langsungnya tanpa lengan default. Untuk detailnya, lihat Pola hierarki tertutup. Pengubah closed tidak berlaku untuk record struct jenis, karena rekaman struktur tidak dapat abstrak.

Parameter posisi dalam jenis rekaman turunan

Rekaman turunan mendeklarasikan parameter posisional untuk semua parameter di konstruktor utama rekaman dasar. Rekaman dasar mendeklarasikan dan menginisialisasi properti tersebut. Rekaman turunan tidak menyembunyikan properti tersebut, tapi hanya membuat dan menginisialisasi properti untuk parameter yang tidak dideklarasikan dalam rekaman dasarnya.

Contoh berikut mengilustrasikan pewarisan dengan sintaksis properti posisi:

public abstract record Person(string FirstName, string LastName);
public record Teacher(string FirstName, string LastName, int Grade)
    : Person(FirstName, LastName);
public static void Main()
{
    Person teacher = new Teacher("Nancy", "Davolio", 3);
    Console.WriteLine(teacher);
    // output: Teacher { FirstName = Nancy, LastName = Davolio, Grade = 3 }
}

Kesetaraan dalam hierarki warisan

Bagian ini berlaku untuk jenis record class, tetapi bukan jenis record struct. Agar dua variabel rekaman sama, jenis runtime harus sama. Jenis variabel yang menampung dapat berbeda. Perbandingan kesetaraan yang diwariskan diilustrasikan dalam contoh kode berikut:

public abstract record Person(string FirstName, string LastName);
public record Teacher(string FirstName, string LastName, int Grade)
    : Person(FirstName, LastName);
public record Student(string FirstName, string LastName, int Grade)
    : Person(FirstName, LastName);
public static void Main()
{
    Person teacher = new Teacher("Nancy", "Davolio", 3);
    Person student = new Student("Nancy", "Davolio", 3);
    Console.WriteLine(teacher == student); // output: False

    Student student2 = new Student("Nancy", "Davolio", 3);
    Console.WriteLine(student2 == student); // output: True
}

Dalam contoh, semua variabel dinyatakan sebagai Person, bahkan ketika instans adalah jenis turunan baik dari Student maupun Teacher. Instans memiliki properti yang sama dan nilai properti yang sama. Tetapi student == teacher mengembalikan False meskipun keduanya adalah Personvariabel -jenis, dan student == student2 mengembalikan True meskipun satu adalah variabel Person dan satu adalah variabel Student. Uji kesetaraan tergantung pada jenis runtime objek yang sebenarnya, bukan jenis variabel yang dideklarasikan.

Untuk mengimplementasikan perilaku ini, kompiler mensintesis properti EqualityContract yang mengembalikan objek Type yang cocok dengan jenis rekaman. EqualityContract memungkinkan metode kesetaraan untuk membandingkan jenis runtime objek saat mereka memeriksa kesetaraan. Jika jenis dasar rekaman adalah object, properti ini adalah virtual. Jika jenis dasar adalah jenis rekaman lain, properti ini adalah penimpaan. Jika jenis rekaman adalah sealed, properti ini secara efektif sealed karena jenisnya adalah sealed.

Ketika kode membandingkan dua instans dari jenis turunan, metode kesetaraan yang disintesis memeriksa semua anggota data dari jenis dasar dan turunan untuk kesetaraan. Metode yang disintesis GetHashCodeGetHashCode menggunakan metode dari semua anggota data yang dideklarasikan dalam jenis dasar dan jenis catatan turunan. Anggota data dari record menyertakan semua bidang yang dideklarasikan dan bidang backing yang disintesis kompilator untuk properti yang diterapkan secara otomatis.

ekspresi with dalam rekaman turunan

Hasil ekspresi with memiliki jenis run-time yang sama dengan operand ekspresi. Semua properti jenis run-time disalin, tetapi Anda hanya dapat mengatur properti jenis waktu kompilasi, seperti yang ditunjukkan contoh berikut:

public record Point(int X, int Y)
{
    public int Zbase { get; set; }
};
public record NamedPoint(string Name, int X, int Y) : Point(X, Y)
{
    public int Zderived { get; set; }
};

public static void Main()
{
    Point p1 = new NamedPoint("A", 1, 2) { Zbase = 3, Zderived = 4 };

    Point p2 = p1 with { X = 5, Y = 6, Zbase = 7 }; // Can't set Name or Zderived
    Console.WriteLine(p2 is NamedPoint);  // output: True
    Console.WriteLine(p2);
    // output: NamedPoint { X = 5, Y = 6, Zbase = 7, Name = A, Zderived = 4 }

    Point p3 = (NamedPoint)p1 with { Name = "B", X = 5, Y = 6, Zbase = 7, Zderived = 8 };
    Console.WriteLine(p3);
    // output: NamedPoint { X = 5, Y = 6, Zbase = 7, Name = B, Zderived = 8 }
}

pemformatan PrintMembers dalam rekaman turunan

Metode PrintMembers yang disintesis dari jenis rekaman turunan memanggil implementasi dasar. Hasilnya adalah bahwa output mencakup semua properti publik dan bidang dari jenis turunan dan dasar, seperti yang ToString ditunjukkan dalam contoh berikut:

public abstract record Person(string FirstName, string LastName);
public record Teacher(string FirstName, string LastName, int Grade)
    : Person(FirstName, LastName);
public record Student(string FirstName, string LastName, int Grade)
    : Person(FirstName, LastName);

public static void Main()
{
    Person teacher = new Teacher("Nancy", "Davolio", 3);
    Console.WriteLine(teacher);
    // output: Teacher { FirstName = Nancy, LastName = Davolio, Grade = 3 }
}

Anda dapat memberikan implementasi dari metode PrintMembers milik Anda sendiri. Jika Anda melakukannya, gunakan tanda tangan berikut:

  • Untuk rekaman sealed yang berasal dari object (tidak mendeklarasikan rekaman dasar): private bool PrintMembers(StringBuilder builder);
  • sealed Untuk catatan yang berasal dari rekaman lain (jenis penutupnya adalah sealed, sehingga metodenya efektif sealed): protected override bool PrintMembers(StringBuilder builder);
  • Untuk rekaman yang bukan sealed dan berasal dari objek: protected virtual bool PrintMembers(StringBuilder builder);
  • Untuk rekaman yang bukan sealed dan berasal dari rekaman lain: protected override bool PrintMembers(StringBuilder builder);

Berikut adalah contoh kode yang menggantikan metode PrintMembers yang disintesis, satu untuk jenis rekaman yang berasal dari objek, dan satu untuk jenis rekaman yang berasal dari rekaman lain:

public abstract record Person(string FirstName, string LastName, string[] PhoneNumbers)
{
    protected virtual bool PrintMembers(StringBuilder stringBuilder)
    {
        stringBuilder.Append($"FirstName = {FirstName}, LastName = {LastName}, ");
        stringBuilder.Append($"PhoneNumber1 = {PhoneNumbers[0]}, PhoneNumber2 = {PhoneNumbers[1]}");
        return true;
    }
}

public record Teacher(string FirstName, string LastName, string[] PhoneNumbers, int Grade)
    : Person(FirstName, LastName, PhoneNumbers)
{
    protected override bool PrintMembers(StringBuilder stringBuilder)
    {
        if (base.PrintMembers(stringBuilder))
        {
            stringBuilder.Append(", ");
        }
        ;
        stringBuilder.Append($"Grade = {Grade}");
        return true;
    }
};

public static void Main()
{
    Person teacher = new Teacher("Nancy", "Davolio", new string[2] { "555-1234", "555-6789" }, 3);
    Console.WriteLine(teacher);
    // output: Teacher { FirstName = Nancy, LastName = Davolio, PhoneNumber1 = 555-1234, PhoneNumber2 = 555-6789, Grade = 3 }
}

Catatan

Kompilator mensintesis PrintMembers dalam rekod turunan bahkan ketika rekod dasar menutup metode ToString. Anda juga dapat membuat implementasi Anda sendiri dari PrintMembers.

Perilaku dekonstruktor dalam rekaman turunan

Metode Deconstruct rekaman turunan mengembalikan nilai semua properti posisi dari jenis waktu kompilasi. Jika jenis variabel adalah rekaman dasar, operasi dekonstruksi hanya mengembalikan properti rekaman dasar kecuali objek ditransmisikan ke jenis turunan. Contoh berikut menunjukkan pemanggilan dekonstruktor pada rekaman turunan.

public abstract record Person(string FirstName, string LastName);
public record Teacher(string FirstName, string LastName, int Grade)
    : Person(FirstName, LastName);
public record Student(string FirstName, string LastName, int Grade)
    : Person(FirstName, LastName);

public static void Main()
{
    Person teacher = new Teacher("Nancy", "Davolio", 3);
    var (firstName, lastName) = teacher; // Doesn't deconstruct Grade
    Console.WriteLine($"{firstName}, {lastName}");// output: Nancy, Davolio

    var (fName, lName, grade) = (Teacher)teacher;
    Console.WriteLine($"{fName}, {lName}, {grade}");// output: Nancy, Davolio, 3
}

Batasan generik

Kata record kunci adalah pengubah untuk class jenis atau struct . Menambahkan pengubah record menyertakan perilaku yang dijelaskan sebelumnya dalam artikel ini. Tidak ada batasan generik yang mengharuskan jenis menjadi rekaman. Memenuhi record class batasan class . Memenuhi record struct batasan struct . Untuk informasi selengkapnya, lihat Batasan pada Parameter Jenis.

Spesifikasi bahasa C#

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

Untuk informasi selengkapnya tentang fitur-fitur ini, lihat catatan proposal fitur berikut ini:

Lihat juga