مقدمه
در بسیاری از سناریوهای برنامهنویسی، هدف اصلی ما از تعریف یک نوع، صرفاً نگهداری داده است. برای
مثال، یک نوع برای نگهداری اطلاعات یک کاربر، یک محصول یا یک نقطه در فضای سهبعدی. تا قبل از
C# 9، برای این کار معمولاً از class یا struct استفاده میکردیم که اغلب منجر به نوشتن کدهای تکراری زیادی
برای سازندهها، پراپرتیها و متدهایی مانند Equals و ToString میشد. C# 9 با معرفی رکوردها
(Records)، یک راهکار مدرن، مختصر و قدرتمند برای تعریف انواعی که ماهیت دادهمحور
دارند، ارائه داد. رکوردها به طور پیشفرض تغییرناپذیر (immutable) هستند و
کامپایلر بسیاری از متدهای مفید را به صورت خودکار برای آنها تولید میکند.
رکورد (Record) چیست؟
یک رکورد، نوع خاصی از class یا struct
است که برای سادهسازی کار با مدلهای داده طراحی شده. هدف اصلی رکوردها کپسوله کردن داده است. وقتی
یک نوع را به عنوان رکورد تعریف میکنید، کامپایلر به طور خودکار قابلیتهای زیر را برای شما فراهم
میکند:
- سینتکس بسیار مختصر برای تعریف نوع و پراپرتیهای آن.
- تغییرناپذیری به صورت پیشفرض.
- مقایسهی مبتنی بر مقدار (Value-based equality) به جای مقایسهی مبتنی بر ارجاع.
- یک پیادهسازی مفید و خوانا برای متد ToString().
- پشتیبانی از "تغییر غیرمخرب" با استفاده از عبارت with.
رکوردهای جایگاهی (Positional Records)
مختصرترین و رایجترین روش برای تعریف یک رکورد، استفاده از سینتکس جایگاهی است. شما میتوانید یک
رکورد کامل را تنها در یک خط تعریف کنید.
Program.cs
public record Person(string FirstName, string LastName);
Person person1 = new Person("John", "Doe");
Console.WriteLine(person1);
خروجی این کد به شکل زیر خواهد بود:
Person { FirstName = John, LastName = Doe }
این یک خط کد public record Person(...) کارهای بسیار زیادی را در پشت صحنه انجام میدهد:
- یک کلاس Person ایجاد میکند.
- یک سازندهی عمومی تعریف میکند که FirstName و LastName را به عنوان پارامتر میپذیرد.
- دو پراپرتی عمومی و init-only به نامهای FirstName و LastName ایجاد
میکند. (یعنی پس از ساخت شیء، قابل تغییر نیستند).
- یک متد Deconstruct برای تجزیهی رکورد ایجاد میکند.
- متدهای Equals، GetHashCode و ToString را به صورت بهینه پیادهسازی میکند.
ویژگیهای تولید شده توسط کامپایلر
بیایید برخی از مهمترین قابلیتهایی که کامپایلر به صورت خودکار برای رکوردها فراهم میکند را با
جزئیات بیشتری بررسی کنیم.
برابری مبتنی بر مقدار (Value-Based Equality)
یکی از بزرگترین تفاوتهای رکوردها با کلاسهای عادی، نحوهی مقایسهی آنهاست. دو متغیر از نوع
کلاس عادی فقط زمانی برابر هستند که به یک شیء واحد در حافظه ارجاع دهند. اما دو متغیر از نوع رکورد
زمانی برابرند که مقادیر تمام پراپرتیهایشان با هم برابر باشد.
Program.cs
public record Person(string FirstName, string LastName);
Person person1 = new("Jane", "Doe");
Person person2 = new("Jane", "Doe");
Console.WriteLine($"Are they equal? {person1 == person2}");
این رفتار برای کار با اشیاء دادهمحور (Data Transfer Objects - DTOs) و در سناریوهایی مانند
تستنویسی بسیار مفید است، زیرا دیگر نیازی به پیادهسازی دستی متد Equals نداریم.
تغییر غیرمخرب با عبارت with
از آنجایی که رکوردها به طور پیشفرض تغییرناپذیر هستند، شما نمیتوانید پس از ایجاد یک شیء،
پراپرتیهای آن را تغییر دهید. اما اگر نیاز داشته باشید یک نسخهی جدید از شیء با چند تغییر جزئی
بسازید چه؟ برای این کار از عبارت with استفاده میکنیم. این عبارت یک
کپی از رکورد اصلی ایجاد کرده و به شما اجازه میدهد مقادیر پراپرتیهای مورد
نظرتان را در نسخهی جدید تغییر دهید.
Program.cs
public record Book(string Title, string Author);
Book originalBook = new("Pride and Prejudice", "Jane Austen");
Book updatedBook = originalBook with { Title = "Sense and Sensibility" };
Console.WriteLine(originalBook);
Console.WriteLine(updatedBook);
خروجی این کد نشان میدهد که شیء اصلی دستنخورده باقی مانده است:
Book { Title = Pride and Prejudice, Author = Jane Austen }
Book { Title = Sense and Sensibility, Author = Jane Austen }
این ویژگی که به آن تغییر غیرمخرب (non-destructive mutation) گفته میشود، یکی از
اصول کلیدی در برنامهنویسی تابعی و کار با دادههای تغییرناپذیر است.