Membuat jenis rekaman
adalah tipe yang menggunakan kesetaraan berbasis nilai . Anda dapat menentukan rekaman sebagai jenis referensi atau jenis nilai. Dua variabel jenis rekaman sama jika definisi jenis rekaman identik, dan jika untuk setiap bidang, nilai di kedua rekaman sama. Dua variabel jenis kelas sama jika objek yang dimaksud adalah jenis kelas yang sama dan variabel merujuk ke objek yang sama. Kesetaraan berbasis nilai menyiratkan kemampuan lain yang mungkin Anda inginkan dalam jenis rekaman. Pengkompilasi menghasilkan banyak anggota tersebut ketika Anda mendeklarasikan record
alih-alih class
. Pengkompilasi menghasilkan metode yang sama untuk jenis record struct
.
Dalam tutorial ini, Anda mempelajari cara:
- Tentukan apakah Anda menambahkan pengubah
record
ke jenisclass
. - Deklarasikan jenis rekaman dan tipe rekaman posisi.
- Ganti metode Anda dengan metode yang dihasilkan oleh pengompiler dalam catatan.
Prasyarat
Anda perlu menyiapkan komputer Anda untuk menjalankan .NET 6 atau yang lebih baru. Pengkompilasi C# tersedia dengan
Karakteristik rekaman
Anda menentukan catatan dengan mendeklarasikan jenis dengan kata kunci record
, memodifikasi deklarasi class
atau struct
. Secara opsional, Anda dapat menghilangkan kata kunci class
untuk membuat record class
. Catatan mengikuti semantik kesetaraan berbasis nilai. Untuk menerapkan semantik nilai, pengkompilasi menghasilkan beberapa metode untuk jenis catatan Anda (baik untuk jenis record class
maupun jenis record struct
):
- Penggantian Object.Equals(Object).
- Metode
Equals
virtual yang parameternya adalah tipe rekaman. - Penggantian terhadap Object.GetHashCode().
- Metode untuk
operator ==
danoperator !=
. - Jenis-jenis rekaman mengimplementasikan System.IEquatable<T>.
Rekaman juga memberikan penggantian untuk Object.ToString(). Kompilator mensintesis metode untuk menampilkan rekaman menggunakan Object.ToString(). Anda menjelajahi anggota tersebut saat menulis kode untuk tutorial ini. Rekaman mendukung ekspresi with
untuk mengaktifkan mutasi rekaman yang tidak merusak.
Anda juga dapat mendeklarasikan rekaman posisi menggunakan sintaks yang lebih ringkas. Kompilator mensintesis lebih banyak metode untuk Anda ketika Anda mendeklarasikan rekaman posisi:
- Konstruktor utama yang parameternya cocok dengan parameter posisi pada deklarasi rekaman.
- Properti publik untuk setiap parameter konstruktor utama. Properti ini khusus init untuk jenis
record class
dan jenisreadonly record struct
. Untuk jenisrecord struct
, mereka baca-tulis. - Metode
Deconstruct
untuk mengekstrak properti dari rekaman.
Membangun data suhu
Data dan statistik adalah salah satu skenario di mana Anda ingin menggunakan rekaman. Untuk tutorial ini, Anda membangun aplikasi yang menghitung derajat hari untuk penggunaan yang berbeda. Derajat hari adalah ukuran panas (atau kurang panas) selama periode hari, minggu, atau bulan. Hari derajat digunakan untuk melacak dan memprediksi penggunaan energi. Hari yang lebih panas berarti lebih banyak AC, dan hari yang lebih dingin berarti lebih banyak penggunaan tungku. Derajat hari membantu mengelola populasi tanaman dan berkorelasi dengan pertumbuhan tanaman saat musim berubah. Derajat hari membantu melacak migrasi hewan untuk spesies yang melakukan perjalanan untuk mencocokkan iklim.
Rumus didasarkan pada suhu rata-rata pada hari tertentu dan suhu garis besar. Untuk menghitung derajat hari dari waktu ke waktu, Anda memerlukan suhu tinggi dan rendah setiap hari untuk jangka waktu tertentu. Mari kita mulai dengan membuat aplikasi baru. Buat aplikasi konsol baru. Buat jenis rekaman baru dalam file baru bernama "DailyTemperature.cs":
public readonly record struct DailyTemperature(double HighTemp, double LowTemp);
Kode sebelumnya mendefinisikan rekaman posisi . Catatan DailyTemperature
adalah readonly record struct
, karena Anda tidak berniat untuk mewarisinya, dan seharusnya tidak dapat diubah. Properti HighTemp
dan LowTemp
adalah properti init hanya , yang berarti properti tersebut hanya dapat diatur pada konstruktor atau dengan menggunakan penginisialisasi properti. Jika Anda ingin parameter posisi menjadi baca-tulis, Anda mendeklarasikan record struct
alih-alih readonly record struct
. Jenis DailyTemperature
juga memiliki konstruktor utama yang memiliki dua parameter yang cocok dengan dua properti. Anda menggunakan konstruktor utama untuk menginisialisasi rekaman DailyTemperature
. Kode berikut membuat dan menginisialisasi beberapa rekaman DailyTemperature
. Yang pertama menggunakan parameter bernama untuk mengklarifikasi HighTemp
dan LowTemp
. Penginisialisasi yang tersisa menggunakan parameter posisi untuk menginisialisasi HighTemp
dan LowTemp
:
private static DailyTemperature[] data = [
new DailyTemperature(HighTemp: 57, LowTemp: 30),
new DailyTemperature(60, 35),
new DailyTemperature(63, 33),
new DailyTemperature(68, 29),
new DailyTemperature(72, 47),
new DailyTemperature(75, 55),
new DailyTemperature(77, 55),
new DailyTemperature(72, 58),
new DailyTemperature(70, 47),
new DailyTemperature(77, 59),
new DailyTemperature(85, 65),
new DailyTemperature(87, 65),
new DailyTemperature(85, 72),
new DailyTemperature(83, 68),
new DailyTemperature(77, 65),
new DailyTemperature(72, 58),
new DailyTemperature(77, 55),
new DailyTemperature(76, 53),
new DailyTemperature(80, 60),
new DailyTemperature(85, 66)
];
Anda dapat menambahkan properti atau metode Anda sendiri ke rekaman, termasuk rekaman posisi. Anda perlu menghitung suhu rata-rata untuk setiap hari. Anda bisa menambahkan properti tersebut ke rekaman DailyTemperature
:
public readonly record struct DailyTemperature(double HighTemp, double LowTemp)
{
public double Mean => (HighTemp + LowTemp) / 2.0;
}
Mari kita pastikan Anda dapat menggunakan data ini. Tambahkan kode berikut ke metode Main
Anda:
foreach (var item in data)
Console.WriteLine(item);
Jalankan aplikasi Anda, dan Anda melihat output yang terlihat mirip dengan tampilan berikut (beberapa baris dihapus untuk spasi):
DailyTemperature { HighTemp = 57, LowTemp = 30, Mean = 43.5 }
DailyTemperature { HighTemp = 60, LowTemp = 35, Mean = 47.5 }
DailyTemperature { HighTemp = 80, LowTemp = 60, Mean = 70 }
DailyTemperature { HighTemp = 85, LowTemp = 66, Mean = 75.5 }
Kode sebelumnya menunjukkan output dari penggantian ToString
yang disintesis oleh pengkompilasi. Jika Anda lebih suka teks yang berbeda, Anda dapat menulis versi ToString
Anda sendiri yang mencegah kompiler mensintesis versi untuk Anda.
Hitung hari derajat
Untuk menghitung derajat hari, Anda mengambil perbedaan dari suhu garis besar dan suhu rata-rata pada hari tertentu. Untuk mengukur panas dari waktu ke waktu, Anda membuang hari di mana suhu rata-rata berada di bawah garis besar. Untuk mengukur suhu dingin dari waktu ke waktu, Anda membuang setiap hari di mana suhu rata-rata berada di atas patokan. Misalnya, AS menggunakan 65 Fahrenheit sebagai dasar untuk derajat hari pemanasan dan pendinginan. Itulah suhu di mana tidak diperlukan pemanasan atau pendinginan. Jika sehari memiliki suhu rata-rata 70 F, hari itu mewakili lima derajat pendinginan dan nol derajat pemanasan. Sebaliknya, jika suhu rata-rata adalah 55 °F, hari itu adalah 10 derajat pemanasan dan 0 derajat pendinginan.
Anda dapat mengekspresikan rumus ini sebagai hierarki kecil dari jenis catatan: jenis hari derajat abstrak dan dua jenis konkret untuk hari derajat pemanasan dan hari derajat pendinginan. Jenis ini juga dapat berupa rekaman posisi. Mereka mengambil suhu garis besar dan urutan rekaman suhu harian sebagai argumen ke konstruktor utama:
public abstract record DegreeDays(double BaseTemperature, IEnumerable<DailyTemperature> TempRecords);
public sealed record HeatingDegreeDays(double BaseTemperature, IEnumerable<DailyTemperature> TempRecords)
: DegreeDays(BaseTemperature, TempRecords)
{
public double DegreeDays => TempRecords.Where(s => s.Mean < BaseTemperature).Sum(s => BaseTemperature - s.Mean);
}
public sealed record CoolingDegreeDays(double BaseTemperature, IEnumerable<DailyTemperature> TempRecords)
: DegreeDays(BaseTemperature, TempRecords)
{
public double DegreeDays => TempRecords.Where(s => s.Mean > BaseTemperature).Sum(s => s.Mean - BaseTemperature);
}
Catatan DegreeDays
abstrak adalah kelas dasar bersama untuk rekaman HeatingDegreeDays
dan CoolingDegreeDays
. Deklarasi konstruktor utama pada rekaman turunan menunjukkan cara mengelola inisialisasi rekaman dasar. Rekaman turunan Anda mendeklarasikan parameter untuk semua parameter di konstruktor utama rekaman dasar. Rekaman dasar mendeklarasikan dan menginisialisasi properti tersebut. Rekaman turunan tidak menyembunyikannya, tetapi hanya membuat dan menginisialisasi properti untuk parameter yang tidak dideklarasikan dalam rekaman dasarnya. Dalam contoh ini, rekaman turunan tidak menambahkan parameter konstruktor utama baru. Uji kode Anda dengan menambahkan kode berikut ke metode Main
Anda:
var heatingDegreeDays = new HeatingDegreeDays(65, data);
Console.WriteLine(heatingDegreeDays);
var coolingDegreeDays = new CoolingDegreeDays(65, data);
Console.WriteLine(coolingDegreeDays);
Anda mendapatkan output seperti tampilan berikut:
HeatingDegreeDays { BaseTemperature = 65, TempRecords = record_types.DailyTemperature[], DegreeDays = 85 }
CoolingDegreeDays { BaseTemperature = 65, TempRecords = record_types.DailyTemperature[], DegreeDays = 71.5 }
Definisikan metode yang disintesis oleh kompilator
Kode Anda menghitung jumlah hari tingkat pemanasan dan pendinginan yang benar selama periode waktu tersebut. Tetapi contoh ini menunjukkan mengapa Anda mungkin ingin mengganti beberapa metode yang disintesis untuk rekaman. Anda dapat mendeklarasikan versi Anda sendiri dari salah satu metode yang disintesis kompilator dalam jenis rekaman kecuali metode kloning. Metode kloning memiliki nama yang dihasilkan kompilator dan Anda tidak dapat memberikan implementasi yang berbeda. Metode yang disintesis ini termasuk konstruktor salinan, anggota antarmuka System.IEquatable<T>, uji kesetaraan dan ketidaksetaraan, dan GetHashCode(). Untuk tujuan ini, Anda mensintesis PrintMembers
. Anda juga dapat mendeklarasikan ToString
Anda sendiri, tetapi PrintMembers
menyediakan opsi yang lebih baik untuk skenario warisan. Untuk menyediakan versi metode sintesis Anda sendiri, tanda tangan harus cocok dengan metode yang disintesis.
Elemen TempRecords
dalam output konsol tidak berguna. Ini menampilkan jenisnya, tetapi tidak ada yang lain. Anda dapat mengubah perilaku ini dengan menyediakan implementasi Anda sendiri dari metode PrintMembers
yang disintesis. Tanda tangan tergantung pada pengubah yang diterapkan ke deklarasi record
:
- Jika jenis catatan adalah
sealed
, ataurecord struct
, maka tanda tangan adalahprivate bool PrintMembers(StringBuilder builder);
- Jika jenis rekaman tidak
sealed
dan berasal dariobject
(artinya, itu tidak mendeklarasikan catatan dasar), tanda tanganprotected virtual bool PrintMembers(StringBuilder builder);
- Jika jenis catatan tidak
sealed
dan berasal dari rekaman lain, tanda tangannya adalahprotected override bool PrintMembers(StringBuilder builder);
Aturan ini paling mudah dipahami melalui pemahaman tujuan PrintMembers
.
PrintMembers
menambahkan informasi tentang setiap properti dalam jenis catatan ke string. Kontrak memerlukan catatan dasar untuk menambahkan anggota mereka ke tampilan dan mengasumsikan anggota turunan menambahkan anggota mereka. Setiap tipe rekaman mensintesis penggantian ToString
yang terlihat mirip seperti contoh berikut untuk HeatingDegreeDays
:
public override string ToString()
{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append("HeatingDegreeDays");
stringBuilder.Append(" { ");
if (PrintMembers(stringBuilder))
{
stringBuilder.Append(" ");
}
stringBuilder.Append("}");
return stringBuilder.ToString();
}
Anda mendeklarasikan metode PrintMembers
dalam record DegreeDays
yang tidak mencetak tipe dari koleksi:
protected virtual bool PrintMembers(StringBuilder stringBuilder)
{
stringBuilder.Append($"BaseTemperature = {BaseTemperature}");
return true;
}
Tanda tangan mendeklarasikan metode virtual protected
agar sesuai dengan versi pengkompilasi. Jangan khawatir jika Anda salah mendapatkan aksesor; bahasa memberlakukan tanda tangan yang benar. Jika Anda lupa pengubah yang benar untuk metode yang disintesis, pengkompilasi mengeluarkan peringatan atau kesalahan yang membantu Anda mendapatkan tanda tangan yang tepat.
Anda dapat mendeklarasikan metode ToString
sebagai sealed
dalam jenis catatan. Hal itu mencegah catatan turunan untuk menyediakan implementasi baru. Rekaman turunan tetap akan berisi penimpaan PrintMembers
. Anda akan menyegel ToString
jika Anda tidak ingin menampilkan jenis runtime rekaman. Dalam contoh sebelumnya, Anda akan kehilangan informasi tentang di mana pengukuran hari derajat pemanasan atau pendinginan dilakukan.
Mutasi tidak merusak
Anggota yang disintesis dalam kelas rekaman posisional tidak memodifikasi status rekaman. Tujuannya adalah agar Anda dapat dengan lebih mudah membuat rekaman yang tidak dapat diubah. Ingatlah bahwa Anda mendeklarasikan readonly record struct
untuk membuat struktur rekaman yang tidak dapat diubah. Lihat lagi deklarasi sebelumnya untuk HeatingDegreeDays
dan CoolingDegreeDays
. Anggota melakukan komputasi terhadap nilai untuk catatan, tetapi tidak mengubah status. Rekaman posisi mempermudah Anda untuk membuat jenis referensi yang tidak dapat diubah.
Membuat jenis referensi yang tidak dapat diubah berarti Anda ingin menggunakan mutasi nondestruktif. Anda membuat instans rekaman baru yang mirip dengan instans rekaman yang ada menggunakan ekspresi with
. Ekspresi ini adalah konstruksi salinan dengan tugas tambahan yang memodifikasi salinan. Hasilnya adalah instans rekaman baru di mana setiap properti disalin dari rekaman yang ada dan dimodifikasi secara opsional. Rekaman asli tidak berubah.
Mari kita tambahkan beberapa fitur ke program Anda yang menunjukkan ekspresi with
. Pertama, mari kita buat rekor baru untuk menghitung derajat hari pertumbuhan menggunakan data yang sama.
Growing degree days biasanya menggunakan 41 F sebagai batas dasar dan mengukur suhu di atas batas dasar. Untuk menggunakan data yang sama, Anda dapat membuat rekaman baru yang mirip dengan coolingDegreeDays
, tetapi dengan suhu dasar yang berbeda:
// Growing degree days measure warming to determine plant growing rates
var growingDegreeDays = coolingDegreeDays with { BaseTemperature = 41 };
Console.WriteLine(growingDegreeDays);
Anda dapat membandingkan jumlah derajat yang dihitung dengan angka yang dihasilkan dengan suhu garis besar yang lebih tinggi. Ingatlah bahwa rekaman jenis referensi dan salinan ini adalah salinan dangkal. Array untuk data tidak disalin, tetapi kedua catatan saling merujuk ke data yang sama. Fakta itu adalah keuntungan dalam satu skenario lain. Untuk hari derajat pertumbuhan, berguna untuk melacak total dalam lima hari terakhir. Anda dapat membuat rekaman baru dengan data sumber yang berbeda menggunakan ekspresi with
. Kode berikut membangun kumpulan akumulasi ini, lalu menampilkan nilai:
// showing moving accumulation of 5 days using range syntax
List<CoolingDegreeDays> movingAccumulation = new();
int rangeSize = (data.Length > 5) ? 5 : data.Length;
for (int start = 0; start < data.Length - rangeSize; start++)
{
var fiveDayTotal = growingDegreeDays with { TempRecords = data[start..(start + rangeSize)] };
movingAccumulation.Add(fiveDayTotal);
}
Console.WriteLine();
Console.WriteLine("Total degree days in the last five days");
foreach(var item in movingAccumulation)
{
Console.WriteLine(item);
}
Anda juga bisa menggunakan ekspresi with
untuk membuat salinan rekaman. Jangan tentukan properti apa pun di antara kurung kurawal untuk ekspresi with
. Itu berarti membuat salinan, dan tidak mengubah properti apa pun:
var growingDegreeDaysCopy = growingDegreeDays with { };
Jalankan aplikasi yang sudah selesai untuk melihat hasilnya.
Ringkasan
Tutorial ini menunjukkan beberapa aspek rekaman. Rekaman menyediakan sintaks ringkas untuk jenis di mana penggunaan mendasar adalah menyimpan data. Untuk kelas berorientasi objek, penggunaan mendasar menentukan tanggung jawab. Tutorial ini berfokus pada catatan posisional, di mana Anda dapat menggunakan sintaksis ringkas untuk mendeklarasikan properti catatan. Pengkompilasi mensintesis beberapa anggota rekaman untuk menyalin dan membandingkan rekaman. Anda dapat menambahkan anggota lain yang Anda butuhkan untuk jenis catatan Anda. Anda dapat membuat jenis rekaman yang tidak dapat diubah mengetahui bahwa tidak ada anggota yang dihasilkan kompilator yang akan bermutasi status. Dan ekspresi with
memudahkan untuk mendukung mutasi yang tidak merusak.
Rekaman menambahkan cara lain untuk menentukan jenis. Anda menggunakan definisi class
untuk membuat hierarki berorientasi objek yang berfokus pada tanggung jawab dan perilaku objek. Anda membuat jenis struct
untuk struktur data yang menyimpan data dan cukup kecil untuk disalin secara efisien. Anda membuat jenis record
saat Anda menginginkan kesetaraan dan perbandingan berbasis nilai, tidak ingin menyalin nilai, dan ingin menggunakan variabel referensi. Anda membuat jenis record struct
saat Anda menginginkan fitur dari catatan untuk jenis yang cukup kecil sehingga dapat disalin secara efisien.
Anda bisa mempelajari lebih lanjut tentang rekaman di artikel referensi bahasa C# untuk tipe rekaman dan spesifikasi tipe rekaman yang diusulkan serta spesifikasi struktur rekaman .