Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Nota
Artikel ini adalah spesifikasi fitur. Spesifikasi berfungsi sebagai dokumen desain untuk fitur tersebut. Ini termasuk perubahan spesifikasi yang diusulkan, bersama dengan informasi yang diperlukan selama desain dan pengembangan fitur. Artikel ini diterbitkan sampai perubahan spesifikasi yang diusulkan diselesaikan dan dimasukkan dalam spesifikasi ECMA saat ini.
Mungkin ada beberapa perbedaan antara spesifikasi fitur dan implementasi yang selesai. Perbedaan tersebut tertuang dalam catatan rapat desain bahasa (LDM) terkait .
Anda dapat mempelajari lebih lanjut tentang proses untuk mengadopsi speklet fitur ke dalam standar bahasa C# dalam artikel tentang spesifikasi .
Masalah juara: https://github.com/dotnet/csharplang/issues/39
Proposal ini melacak spesifikasi untuk fitur rekaman C# 9, seperti yang disepakati oleh tim desain bahasa C#.
Sintaks untuk rekaman adalah sebagai berikut:
record_declaration
: attributes? class_modifier* 'partial'? 'record' identifier type_parameter_list?
parameter_list? record_base? type_parameter_constraints_clause* record_body
;
record_base
: ':' class_type argument_list?
| ':' interface_type_list
| ':' class_type argument_list? ',' interface_type_list
;
record_body
: '{' class_member_declaration* '}' ';'?
| ';'
;
Jenis catatan adalah jenis referensi, mirip dengan deklarasi kelas. Ini adalah sebuah kesalahan jika catatan memberikan record_baseargument_list jika record_declaration tidak berisi parameter_list.
Hanya boleh ada satu deklarasi jenis parsial untuk sebuah rekaman parsial yang dapat memberikan parameter_list.
Parameter rekaman tidak dapat menggunakan pengubah ref, out, atau this (tetapi in dan params diizinkan).
Warisan
Rekaman tidak dapat mewarisi dari kelas, kecuali kelas object, dan kelas tidak dapat mewarisi dari rekaman. Rekaman bisa mewarisi dari rekaman lain.
Anggota tipe catatan
Selain anggota yang dideklarasikan dalam isi rekaman, jenis rekaman memiliki anggota sintesis tambahan. Anggota akan disintesis kecuali jika ada anggota dengan "tanda tangan yang sesuai" yang dinyatakan dalam badan rekaman atau jika anggota non-virtual konkret yang dapat diakses dengan "tanda tangan yang sesuai" diwarisi. Anggota yang cocok mencegah pengkompilasi menghasilkan anggota tersebut, bukan anggota sintesis lainnya. Dua anggota dianggap cocok jika mereka memiliki tanda tangan yang sama atau akan dianggap "bersembunyi" dalam skenario warisan. Ini adalah kesalahan bagi anggota rekaman untuk dinamai "Clone". Ini adalah kesalahan untuk bidang instans rekaman untuk memiliki jenis penunjuk tingkat atas. Jenis pointer bersarang, seperti array pointer, diizinkan.
Anggota yang disintesis adalah sebagai berikut:
Anggota kesetaraan
Jika catatan berasal dari object, jenis catatan menyertakan properti baca-saja rekaan yang setara dengan properti yang dideklarasikan seperti berikut ini:
Type EqualityContract { get; }
Properti itu adalah private jika jenis catatan itu adalah sealed. Jika tidak, properti virtual dan protected.
Properti dapat dinyatakan secara eksplisit. Ini adalah kesalahan jika deklarasi eksplisit tidak cocok dengan tanda tangan atau aksesibilitas yang diharapkan, atau jika deklarasi eksplisit tidak memungkinkan pengesampingannya dalam jenis turunan dan jenis catatan tidak sealed.
Jika jenis catatan berasal dari jenis catatan dasar Base, jenis catatan menyertakan properti baca-saja yang disintesis yang setara dengan properti yang dideklarasikan sebagai berikut:
protected override Type EqualityContract { get; }
Properti dapat dinyatakan secara eksplisit. Ini adalah kesalahan jika deklarasi eksplisit tidak cocok dengan tanda tangan atau aksesibilitas yang diharapkan, atau jika deklarasi eksplisit tidak memungkinkan pengesampingannya dalam jenis turunan dan jenis catatan tidak sealed. Ini adalah kesalahan jika properti yang disintesis atau dinyatakan secara eksplisit tidak menimpa properti dengan tanda tangan ini dalam tipe rekaman Base (misalnya jika properti hilang di Base, atau disegel, atau tidak virtual, dll.).
Properti yang disintesis mengembalikan typeof(R) di mana R adalah jenis rekaman.
Jenis catatan mengimplementasikan System.IEquatable<R> dan menyertakan kelebihan Equals(R? other) yang diketik kuat yang disintesis di mana R adalah jenis catatan.
Metodenya adalah public, dan metodenya adalah virtual kecuali jenis catatannya adalah sealed.
Metode ini dapat dinyatakan secara eksplisit. Ini adalah kesalahan jika deklarasi eksplisit tidak cocok dengan tanda tangan atau aksesibilitas yang diharapkan, atau deklarasi eksplisit tidak dapat di-override pada jenis turunan dan tipe rekaman bukan sealed.
Jika Equals(R? other) ditentukan pengguna (tidak disintesis) tetapi GetHashCode tidak, peringatan akan dihasilkan.
public virtual bool Equals(R? other);
Equals(R?) yang disintesis mengembalikan true jika dan hanya jika semua yang berikut adalah true.
-
othertidaknull, dan - Untuk setiap bidang instans
fieldNdalam jenis catatan yang tidak diwarisi, nilaiSystem.Collections.Generic.EqualityComparer<TN>.Default.Equals(fieldN, other.fieldN)di manaTNadalah jenis bidang, dan - Jika ada tipe catatan dasar, nilai
base.Equals(other)(panggilan non-virtual kepublic virtual bool Equals(Base? other)); jika tidak, nilaiEqualityContract == other.EqualityContract.
Jenis rekaman mencakup operator == dan != yang disintesis yang setara dengan operator yang dinyatakan sebagai berikut:
public static bool operator==(R? left, R? right)
=> (object)left == right || (left?.Equals(right) ?? false);
public static bool operator!=(R? left, R? right)
=> !(left == right);
Metode Equals yang dipanggil oleh operator == adalah metode Equals(R? other) yang ditentukan di atas. Operator != mendelegasikan ke operator ==. Ini adalah kesalahan jika operator dinyatakan secara eksplisit.
Jika tipe rekaman diturunkan dari tipe rekaman dasar Base, tipe rekaman mencakup penimpaan sintetik yang setara dengan metode yang dideklarasikan sebagai berikut:
public sealed override bool Equals(Base? other);
Ini adalah kesalahan jika penggantian dinyatakan secara eksplisit. Ini adalah kesalahan jika metode tidak mengambil alih metode dengan tanda tangan yang sama dalam jenis rekaman Base (misalnya, jika metode hilang di Base, atau disegel, atau bukan virtual, dll.).
Penggantian yang disintesis mengembalikan Equals((object?)other).
Jenis catatan menyertakan penimpaan yang disintesis yang setara dengan metode yang dinyatakan sebagai berikut:
public override bool Equals(object? obj);
Ini adalah kesalahan jika penggantian dinyatakan secara eksplisit. Ini adalah kesalahan jika metode tidak mengambil alih object.Equals(object? obj) (misalnya, karena bayangan dalam jenis dasar menengah, dll.).
Penggantian yang disintesis mengembalikan Equals(other as R), tempat R adalah jenis rekaman.
Jenis catatan menyertakan penimpaan yang disintesis yang setara dengan metode yang dinyatakan sebagai berikut:
public override int GetHashCode();
Metode ini dapat dinyatakan secara eksplisit.
Merupakan kesalahan jika deklarasi eksplisit tidak memungkinkan penimpaan pada tipe turunan dan tipe rekaman tersebut bukan sealed. Ini adalah kesalahan jika metode yang disintesis atau dinyatakan secara eksplisit tidak mengganti object.GetHashCode() (misalnya, karena terdapat penimpaan pada tipe dasar menengah, dll.).
Peringatan dilaporkan jika salah satu dari Equals(R?) dan GetHashCode() dinyatakan secara eksplisit sedangkan metode lainnya tidak eksplisit.
Penggantian GetHashCode() yang disintesis mengembalikan hasil int dengan menggabungkan nilai-nilai berikut:
- Untuk setiap bidang instans
fieldNdalam jenis catatan yang tidak diwarisi, nilaiSystem.Collections.Generic.EqualityComparer<TN>.Default.GetHashCode(fieldN)di manaTNadalah jenis bidang, dan - Jika ada jenis catatan dasar, nilai
base.GetHashCode(); jika tidak, nilaiSystem.Collections.Generic.EqualityComparer<System.Type>.Default.GetHashCode(EqualityContract).
Misalnya, pertimbangkan jenis catatan berikut:
record R1(T1 P1);
record R2(T1 P1, T2 P2) : R1(P1);
record R3(T1 P1, T2 P2, T3 P3) : R2(P1, P2);
Untuk jenis catatan tersebut, anggota pembanding yang disintesis akan menjadi sesuatu seperti:
class R1 : IEquatable<R1>
{
public T1 P1 { get; init; }
protected virtual Type EqualityContract => typeof(R1);
public override bool Equals(object? obj) => Equals(obj as R1);
public virtual bool Equals(R1? other)
{
return !(other is null) &&
EqualityContract == other.EqualityContract &&
EqualityComparer<T1>.Default.Equals(P1, other.P1);
}
public static bool operator==(R1? left, R1? right)
=> (object)left == right || (left?.Equals(right) ?? false);
public static bool operator!=(R1? left, R1? right)
=> !(left == right);
public override int GetHashCode()
{
return HashCode.Combine(EqualityComparer<Type>.Default.GetHashCode(EqualityContract),
EqualityComparer<T1>.Default.GetHashCode(P1));
}
}
class R2 : R1, IEquatable<R2>
{
public T2 P2 { get; init; }
protected override Type EqualityContract => typeof(R2);
public override bool Equals(object? obj) => Equals(obj as R2);
public sealed override bool Equals(R1? other) => Equals((object?)other);
public virtual bool Equals(R2? other)
{
return base.Equals((R1?)other) &&
EqualityComparer<T2>.Default.Equals(P2, other.P2);
}
public static bool operator==(R2? left, R2? right)
=> (object)left == right || (left?.Equals(right) ?? false);
public static bool operator!=(R2? left, R2? right)
=> !(left == right);
public override int GetHashCode()
{
return HashCode.Combine(base.GetHashCode(),
EqualityComparer<T2>.Default.GetHashCode(P2));
}
}
class R3 : R2, IEquatable<R3>
{
public T3 P3 { get; init; }
protected override Type EqualityContract => typeof(R3);
public override bool Equals(object? obj) => Equals(obj as R3);
public sealed override bool Equals(R2? other) => Equals((object?)other);
public virtual bool Equals(R3? other)
{
return base.Equals((R2?)other) &&
EqualityComparer<T3>.Default.Equals(P3, other.P3);
}
public static bool operator==(R3? left, R3? right)
=> (object)left == right || (left?.Equals(right) ?? false);
public static bool operator!=(R3? left, R3? right)
=> !(left == right);
public override int GetHashCode()
{
return HashCode.Combine(base.GetHashCode(),
EqualityComparer<T3>.Default.GetHashCode(P3));
}
}
Menyalin dan Mengkloning anggota
Jenis catatan berisi dua elemen salinan.
- Konstruktor mengambil argumen tunggal dari jenis rekaman. Ini disebut sebagai "konstruktor salin".
- Metode "klon" instans tanpa parameter yang disintesis dengan nama yang dicadangkan oleh kompilator.
Tujuan dari konstruktor salin adalah untuk menyalin status dari parameter ke instans baru yang dibuat. Konstruktor ini tidak menjalankan penginisialisasi bidang/properti instans apa pun yang ada dalam deklarasi rekaman. Jika konstruktor tidak dideklarasikan secara eksplisit, konstruktor akan disintesis oleh kompilator. Jika catatan disegel, konstruktor akan bersifat pribadi, jika tidak, catatan akan dilindungi. Konstruktor salinan yang dinyatakan secara eksplisit harus publik atau dilindungi, kecuali rekaman disegel. Hal pertama yang harus dilakukan konstruktor, adalah memanggil konstruktor salinan dasar, atau konstruktor objek tanpa parameter jika rekaman mewarisi dari objek. Kesalahan dilaporkan jika konstruktor salinan yang ditentukan pengguna menggunakan inisialisasi konstruktor implisit atau eksplisit yang tidak memenuhi persyaratan ini. Setelah konstruktor salinan dasar dipanggil, konstruktor salinan yang disintesis menyalin nilai untuk semua bidang instans yang dideklarasikan baik secara implisit maupun eksplisit dalam jenis catatan. Satu-satunya kehadiran konstruktor salinan, baik eksplisit atau implisit, tidak mencegah penambahan otomatis konstruktor instans default.
Jika metode "klon" virtual ada dalam rekaman dasar, metode "klon" yang dihasilkan secara otomatis mengambil alihnya dan jenis pengembalian dari metode tersebut adalah jenis yang saat ini memuat. Kesalahan dihasilkan jika metode klon rekaman dasar disegel. Jika metode 'clone' virtual tidak ada dalam record dasar, maka jenis pengembalian metode clone adalah tipe yang mengandung dan metode tersebut adalah virtual, kecuali record tersebut disegel atau abstrak. Jika rekaman yang berisi abstrak, metode kloning yang disintesis juga abstrak. Jika metode "kloning" tidak abstrak, metode tersebut mengembalikan hasil panggilan ke konstruktor salinan.
Mencetak anggota: Metode PrintMembers dan ToString
Jika rekaman berasal dari object, rekaman menyertakan metode yang disintesis yang setara dengan metode yang dinyatakan sebagai berikut:
bool PrintMembers(System.Text.StringBuilder builder);
Jika jenis catatan adalah private, maka metodenya adalah sealed. Jika tidak, metode ini virtual dan protected.
Metode:
- memanggil metode
System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack()jika metodenya ada dan catatan memiliki anggota yang dapat dicetak. - untuk setiap anggota rekaman yang dapat dicetak (kolom publik non-statis dan anggota properti yang dapat dibaca), menambahkan nama anggota tersebut diikuti oleh " = " diikuti oleh nilai anggota yang dipisahkan oleh ", ".
- mengembalikan benar jika rekaman memiliki anggota yang dapat dicetak.
Untuk anggota yang memiliki jenis nilai, kami akan mengonversi nilainya menjadi representasi string menggunakan metode paling efisien yang tersedia untuk platform target. Saat ini berarti memanggil ToString sebelum meneruskan ke StringBuilder.Append.
Jika jenis catatan berasal dari catatan dasar Base, catatan tersebut menyertakan penimpaan yang disintesis setara dengan metode yang diterangkan sebagai berikut:
protected override bool PrintMembers(StringBuilder builder);
Jika rekaman tidak memiliki anggota yang dapat dicetak, metode ini akan memanggil metode dasar PrintMembers dengan satu argumen (parameternya builder) dan mengembalikan hasilnya.
Jika tidak, metode :
- memanggil metode
PrintMembersdasar dengan satu argumen (parameterbuilder), - jika metode
PrintMembersmengembalikan true, tambahkan ", " ke objek builder, - untuk setiap anggota yang dapat dicetak dari rekaman, menyisipkan nama anggota tersebut diikuti dengan " = " kemudian diikuti oleh nilai anggota:
this.member(atauthis.member.ToString()untuk jenis nilai), dan dipisahkan dengan ", ", - mengembalikan benar.
Metode PrintMembers dapat dinyatakan secara eksplisit.
Ini adalah kesalahan jika deklarasi eksplisit tidak cocok dengan tanda tangan atau aksesibilitas yang diharapkan, atau jika deklarasi eksplisit tidak memungkinkan pengesampingannya dalam jenis turunan dan jenis catatan tidak sealed.
Catatan ini mencakup metode yang disintesis yang setara dengan metode yang dinyatakan sebagai berikut:
public override string ToString();
Metode ini dapat dinyatakan secara eksplisit. Ini adalah kesalahan jika deklarasi eksplisit tidak cocok dengan tanda tangan atau aksesibilitas yang diharapkan, atau jika deklarasi eksplisit tidak memungkinkan pengesampingannya dalam jenis turunan dan jenis catatan tidak sealed. Ini adalah kesalahan jika metode yang disintesis atau dinyatakan secara eksplisit tidak mengganti object.ToString() (misalnya, karena terdapat penimpaan pada tipe dasar menengah, dll.).
Metode yang disintesis:
- membuat instans
StringBuilder, - menambahkan nama rekaman ke penyusun, diikuti dengan " { ",
- memanggil metode
PrintMemberscatatan dengan memberikan builder, diikuti dengan " " jika mengembalikan true, - menambahkan "}",
- mengembalikan konten pembangun berupa
builder.ToString().
Misalnya, pertimbangkan jenis catatan berikut:
record R1(T1 P1);
record R2(T1 P1, T2 P2, T3 P3) : R1(P1);
Untuk jenis rekaman tersebut, anggota pencetakan yang tersintesis akan menjadi sesuatu seperti:
class R1 : IEquatable<R1>
{
public T1 P1 { get; init; }
protected virtual bool PrintMembers(StringBuilder builder)
{
builder.Append(nameof(P1));
builder.Append(" = ");
builder.Append(this.P1); // or builder.Append(this.P1.ToString()); if T1 is a value type
return true;
}
public override string ToString()
{
var builder = new StringBuilder();
builder.Append(nameof(R1));
builder.Append(" { ");
if (PrintMembers(builder))
builder.Append(" ");
builder.Append("}");
return builder.ToString();
}
}
class R2 : R1, IEquatable<R2>
{
public T2 P2 { get; init; }
public T3 P3 { get; init; }
protected override bool PrintMembers(StringBuilder builder)
{
if (base.PrintMembers(builder))
builder.Append(", ");
builder.Append(nameof(P2));
builder.Append(" = ");
builder.Append(this.P2); // or builder.Append(this.P2); if T2 is a value type
builder.Append(", ");
builder.Append(nameof(P3));
builder.Append(" = ");
builder.Append(this.P3); // or builder.Append(this.P3); if T3 is a value type
return true;
}
public override string ToString()
{
var builder = new StringBuilder();
builder.Append(nameof(R2));
builder.Append(" { ");
if (PrintMembers(builder))
builder.Append(" ");
builder.Append("}");
return builder.ToString();
}
}
Anggota rekam posisional
Selain anggota yang telah disebutkan, rekaman dengan daftar parameter ("rekaman posisi") menyusun anggota tambahan dengan kondisi yang sama seperti yang disebutkan sebelumnya.
Konstruktor Utama
Jenis catatan memiliki konstruktor publik yang tanda tangannya sesuai dengan parameter nilai deklarasi jenis. Ini disebut konstruktor utama untuk tipe tersebut, dan menyebabkan konstruktor kelas bawaan yang secara implisit dinyatakan, jika ada, dihilangkan. Adalah sebuah kesalahan jika terdapat konstruktor utama dan konstruktor lain dengan signature yang sama yang sudah ada dalam kelas.
Pada saat runtime, konstruktor utama
menjalankan penginisialisasi instans yang terdapat di dalam badan kelas
memanggil konstruktor kelas dasar dengan argumen yang disediakan dalam klausa
record_base, jika ada
Jika sebuah rekaman memiliki konstruktor utama, setiap konstruktor yang ditentukan pengguna kecuali "konstruktor penyalinan" harus memiliki penginisialisasi konstruktor this yang eksplisit.
Parameter konstruktor utama serta anggota catatan berada dalam lingkup dalam argument_list klausa record_base dan dalam penginisialisasi bidang atau properti instans. Anggota instans akan menjadi kesalahan pada lokasi ini (mirip dengan bagaimana anggota instans berada dalam cakupan dalam penginisialisasi konstruktor reguler hari ini, tetapi menjadi kesalahan jika digunakan), akan tetapi, parameter konstruktor utama akan berada dalam cakupan, dapat digunakan dan akan membayangi anggota. Anggota statis juga akan dapat digunakan, mirip dengan cara kerja panggilan dasar dan penginisialisasi di konstruktor biasa saat ini.
Peringatan dihasilkan jika parameter konstruktor utama tidak dibaca.
Variabel ekspresi yang dideklarasikan dalam argument_list berada dalam cakupan dalam argument_list. Aturan bayangan yang sama seperti dalam daftar argumen penginisialisasi konstruktor reguler berlaku.
Properti
Untuk setiap parameter rekaman dari deklarasi jenis rekaman, ada anggota properti publik terkait yang nama dan jenisnya diambil dari deklarasi parameter nilai.
Sebagai catatan:
- Properti
getpublik daninitotomatis dibuat (lihat spesifikasi aksesorinitterpisah). Propertiabstractyang diwariskan dengan jenis yang cocok diganti. Ini adalah kesalahan jika properti yang diwariskan tidak memilikipublicyang dapat digantigetdan aksesorinit. Ini adalah kesalahan jika properti yang diwariskan disembunyikan.
Atribut otomatis diinisialisasi ke nilai parameter konstruktor primer yang sesuai. Atribut dapat diterapkan ke properti otomatis yang disintesis dan bidang dukungannya dengan menggunakan targetproperty:ataufield:untuk atribut yang diterapkan secara sintetis ke parameter rekaman yang sesuai.
Mendekonstruksi
Rekam jejak posisional dengan setidaknya satu parameter menyusun metode instans publik yang mengembalikan void bernama Deconstruct, dengan deklarasi parameter keluar untuk setiap parameter dari deklarasi konstruktor utama. Setiap parameter metode Deconstruct memiliki jenis yang sama dengan parameter yang sesuai dari deklarasi konstruktor utama. Isi dari metode tersebut menetapkan setiap parameter dari metode Deconstruct dengan nilai properti instans yang memiliki nama sama.
Metode ini dapat dinyatakan secara eksplisit. Ini adalah kesalahan jika deklarasi eksplisit tidak cocok dengan tanda tangan atau aksesibilitas yang diharapkan, atau statis.
Contoh berikut menunjukkan rekaman posisi R dengan metode Deconstruct yang disintesis kompilatornya, bersama dengan penggunaannya:
public record R(int P1, string P2 = "xyz")
{
public void Deconstruct(out int P1, out string P2)
{
P1 = this.P1;
P2 = this.P2;
}
}
class Program
{
static void Main()
{
R r = new R(12);
(int p1, string p2) = r;
Console.WriteLine($"p1: {p1}, p2: {p2}");
}
}
ekspresi with
Ekspresi with adalah ekspresi baru menggunakan sintaks berikut.
with_expression
: switch_expression
| switch_expression 'with' '{' member_initializer_list? '}'
;
member_initializer_list
: member_initializer (',' member_initializer)*
;
member_initializer
: identifier '=' expression
;
Ekspresi with tidak diizinkan sebagai pernyataan.
Ekspresi with memungkinkan "mutasi yang tidak merusak", yang dirancang untuk menghasilkan salinan dari ekspresi penerima dengan modifikasi dalam penetapan di member_initializer_list.
Ekspresi with yang valid memiliki penerima dengan jenis non-void. Jenis penerima harus berupa rekaman.
Di sisi kanan dari ekspresi with terdapat member_initializer_list dengan serangkaian penugasan ke pengidentifikasi , yang harus menjadi instance field atau properti yang dapat diakses dari tipe penerima.
Pertama, metode "kloning" milik penerima (ditentukan di atas) dipanggil dan hasilnya dikonversi ke tipe penerima. Kemudian, setiap member_initializer diproses dengan cara yang sama dengan penugasan ke bidang atau akses properti dari hasil konversi. Penugasan diproses dalam urutan leksikal.
C# feature specifications