نظرات مطالب
آشنایی با NHibernate - قسمت پنجم
NH 3.0 پشتیبانی یکپارچه‌ای را از LINQ ارائه می‌دهد و دیگر نیازی نیست مانند نگارش 2 آن پروایدر مخصوصی را جداگانه دریافت کرد. آن پروایدر قدیمی هم به نظر کنار گذاشته شده و از یک کتابخانه‌ی پخته‌تر به نام Remotion Linq Library استفاده گردیده است (+).
در این نگارش برای دسترسی به IQueryable interface می‌توان از متد session.Query استفاده کرد (بجای session.Linq نگارش قبلی).
اشتراک‌ها
Entity Framework Core 7 نسخه نهایی

Entity Framework Core (EF Core) 7 is available on NuGet today!

EF Core 7 is the successor to EF Core 6, and can be referred to as EF7 for brevity. EF Core 7 contains many features that help in porting “classic” EF6 applications to use EF7. As such, we encourage people to upgrade existing classic EF applications to use EF7 where possible. See Porting from EF6 to EF Core for more information. 

Entity Framework Core 7 نسخه نهایی
نظرات مطالب
EF Code First #7
- در قسمت HasRequired که Username نباید تعریف شود. در اینجا یک سر دیگر رابطه باید معرفی گردد. همان روابط و کلاس‌هایی که به صورت virtual در کدها آمده.
- در متن ذکر کردم «همین میزان تنظیم کفایت می‌کند و نیازی به استفاده از Fluent API برای معرفی روابط نیست.»
برای بسیاری از تنظیمات EF Code first، اگر پیش فرض‌های آن‌را رعایت کنید، نیازی به هیچگونه تنظیم اضافه‌تری ندارید. مثلا برای رابطه one-to-many فقط کافی است در دو سر رابطه (نه فقط یک سر آن)، تنظیمات زیر را داشته باشید:
// یک سایت که چندین بلاگ دارد
public class Site
{
    public int Id { get; set; }
    public string Name { get; set; }

    public virtual ICollection<Blog> Blogs { set; get; }
}

public class Blog
{
    public int Id { get; set; }
    public string Name { set; get; }
 
    [ForeignKey("SiteId")]
    public virtual Site Site { get; set; }
    public int SiteId { set; get; }
}
همین مقدار کافی است و پیش فرض‌ها را پوشش می‌دهد. تنظیمات Fluent برای زمانی است که می‌خواهید پیش‌فرض‌ها را بازنویسی کنید. مثلا نام جدول خودکار تشکیل شده توسط آن مدنظر شما نیست. یا حالت بسیار خاصی از روابط مانند مدل‌های خودارجاع دهنده باید تشکیل شود و در این حالت فقط حالت Fluent است که پاسخگوی یک چنین سناریوهایی است.
اشتراک‌ها
تولید URLهای strongly typed در برنامه‌های Blazor WASM

یکی از مشکلات کار با برنامه‌های Blazor WASM، نیاز به کار با آدرس‌های رشته‌ای مانند زیر است:

var secretUrl = "api/WeatherForecast/_secretUrl";
کتابخانه‌ی فوق، این مشکل را برطرف کرده و به صورت خودکار بر اساس Action Methods موجود، نمونه‌ی strongly typed آن‌ها را تولید می‌کند:
var secretUrl = ApiUrls.WeatherForecast.HttpGet.SecretUrl;
تولید URLهای strongly typed در برنامه‌های Blazor WASM
مطالب
مروری بر چند تجربه‌ی کاری با SQLite




SQLite بانک اطلاعاتی سریع، کم حجم و سورس بازی است که استفاده از آن در دات نت فریم ورک بسیار ساده است. فقط کافی است پروایدر مربوط به آن را دریافت کنید و در کدهای قدیمی خود هر جایی مثلا sqlconnection داشتید آن‌را تبدیل به sqliteconnection کنید و امثال آن (به بیان دیگر، پروایدر تهیه شده از معماری ADO.NET پیروی می‌کند و عملا دانش قبلی شما به سادگی قابل استفاده و ارتقاء است). علاوه بر آن پروایدر ADO.NET‌ تهیه شده برای آن، پشتیبانی از Entity framework را هم ارائه می‌دهد.
این دیتابیس تحت سیستم عامل‌های مختلف مهیا است و مهم‌ترین مزیت آن عدم نیاز به نصب آن می‌باشد.

مزایا:
- سورس باز و رایگان
- مهیا بودن آن در سایر پلتفرم‌ها (ویندوز، لینوکس و ...)
- نیازی به نصب ندارد و فقط یک DLL بومی است. این مورد برای کاربرانی که در مدیریت بانک‌های اطلاعاتی پیچیده مشکل دارند، یک مزیت مهم است.
- امکان تشیکل دیتابیس در حافظه. این نکته و توانایی، در آزمون‌های واحد بسیار جالب توجه است. می‌توانید با سرعت بالا دیتابیسی واقعی را در حافظه تشکیل داده، کلیه آزمون‌های واحد خود را اجرا کرده و پس از پایان کار، اثری از دیتابیس و تغییر داده‌ها و مشکلات بازگردانی اطلاعات به حالت اول وجود نخواهد داشت.


ملاحظات:
الف) مرتب سازی SQLite حساس به حروف کوچک و بزرگ است.
برای برگشت به عادت متداولی که وجود دارد می‌شود به صورت زیر عمل کرد:
select f1 from tbl1 order by f1 COLLATE NOCASE
یک COLLATE NOCASE اضافه شده است.

ب) رعایت نکات مرتبط با سیستم‌های 64 بیتی
در مورد سیستم‌های 64 بیتی و دات نت قبلا مطلبی را نوشته بودم : {+}. این مطلب دقیقا اینجا کاربرد پیدا می‌کند، از این لحاظ که SQLite یک بانک اطلاعاتی Native است. اگر برنامه‌ی دات نت شما برای حالت Any CPU تهیه شده است، در سیستم‌های 32 بیتی نیاز است تا DLL مرتبط SQLite را توزیع کنید و در سیستم‌های 64 بیتی DLL مرتبط 64 بیتی آن نیاز خواهد بود. در غیراینصورت برنامه‌ی شما در بدو امر کرش کرده و اجرا نخواهد شد.

مشکلات:
الف) کلید خارجی بی خاصیت!
SQLite از کلید خارجی پشتیبانی می‌کند اما آن‌را اعمال نمی‌کند! برای اینکه کلید خارجی را اعمال کنید باید خودتان تریگر بنویسید تا این‌کار را انجام دهد.

ب) پشتیبانی در حد صفر از مباحث همزمانی و تردینگ.
اگر برنامه شما مالتی ترد است، در بد مخمصه‌ای گرفتار شده‌اید. مدام با پیغام database is locked مواجه خواهید شد. (چه انتظاری داشتید؟ یک dll کمتر از 2 مگابایت که قرار نیست کار غول‌های دیتابیسی را انجام دهد)
بنابراین اصلا تصورش را هم نکنید که از این دیتابیس به عنوان بانک اطلاعاتی یک سایت (و محیط‌های چند کاربره) بتوان استفاده کرد و کاربران دچار مشکل نشوند.

ج)حجم بالای دیتابیس و عدم کش
از مباحث caching که در دیتابیس‌های معظم دیگر به صورت توکار وجود دارد خبری نیست. برای مثال اگر یک کوئری قرار است تعدادی را شمارش نماید، حاصلی کش نشده و اگر صدبار هم به صورت متوالی آن‌را فراخوانی کنید باز هم از نو محاسبات آن انجام خواهد شد.
این مورد در حجم بالای دیتابیس واقعا مهم است و نمودش را با دیتابیسی با حجم بالای یک گیگ به وضوح مشاهده خواهید. افت کارآیی و همچنین قرچ و قرچ مداوم هارد دیسک سیستم! (چون به کش رجوع نمی‌شود)

د) امنیت
روی بانک‌های اطلاعاتی اکسس حداقل می‌توان یک کلمه‌ی عبور را قرار داد (که در کسری از ثانیه قابل شکستن است!). در SQLite استاندارد هیچ خبری از این مباحث نبوده و امنیت را باید خودتان تامین کنید. (البته یک نسخه‌ی تجاری هم از این بانک اطلاعاتی با پشتیبانی از رمزنگاری اطلاعات موجود است : +)

ه) مرتب سازی فارسی
هر چند SQLite هیچ مشکلی در ثبت اطلاعات یونیکد و خصوصا متون فارسی ندارد، اما با مرتب سازی کلمات یونیکد مشکل داشته و بر اساس کد اسکی آن‌ها عمل می‌کند. هر چند امکان تعریف Collation سفارشی در آن ممکن است : + (البته ممکن بودن با موجود بودن متفاوت است)


نتیجه گیری:
- SQLite برای دیتابیسی تا حدود یک گیگ که فقط یک نفر قرار باشد از آن استفاده کند انتخاب بسیار مناسبی است (برای مثال فایرفاکس از آن برای ذخیره سازی تنظیمات خودش استفاده می‌کند).

اشتراک‌ها
Decrypting/Encrypting تنظیمات برنامه در ASP.NET CORE
مطالب
امکان تعریف ساده‌تر کلاس‌های Immutable در C# 9.0 با معرفی نوع جدید record
در مطلب معرفی خواص init-only، با روش معرفی خواص immutable آشنا شدیم. نوع جدیدی که به C# 9.0 به نام record اضافه شده‌است، قسمتی از آن بر اساس همان خواص init-only کار می‌کند. به همین جهت مطالعه‌ی آن مطلب، پیش از ادامه‌ی بحث جاری، ضروری است.


چرا در C# 9.0 تا این اندازه بر روی سادگی ایجاد اشیاء Immutable تمرکز شده‌است؟

به شیءای Immutable گفته می‌شود که پس از وهله سازی ابتدایی آن، وضعیت آن دیگر قابل تغییر نباشد. همچنین به کلاسی Immutable گفته می‌شود که تمام وهله‌های ساخته شده‌ی از آن نیز Immutable باشند. نمونه‌ی یک چنین شیءای را از نگارش 1 دات نت در حال استفاده هستیم: رشته‌ها. رشته‌ها در دات نت غیرقابل تغییر هستند و هرگونه تغییری بر روی آن‌ها، سبب ایجاد یک رشته‌ی جدید (یک شیء جدید) می‌شود. نوع جدید record نیز به همین صورت عمل می‌کند.

مزایای وجود Immutability:

- اشیاء Immutable یا غیرقابل تغییر، thread-safe هستند که در نتیجه، برنامه نویسی همزمان و موازی را بسیار ساده می‌کنند؛ چون چندین thread می‌توانند با شیءای کار کنند که دسترسی به آن، تنها read-only است.
- اشیاء Immutable از اثرات جانبی، مانند تغییرات آن‌ها در متدهای مختلف در امان هستند. می‌توانید آن‌ها را به هر متدی ارسال کنید و مطمئن باشید که پس از پایان کار، این شیء تغییری نکرده‌است.
- کار با اشیاء Immutable، امکان بهینه سازی حافظه را میسر می‌کنند. برای مثال NET runtime.، هش رشته‌های تعریف شده‌ی در برنامه را در پشت صحنه نگهداری می‌کند تا مطمئن شود که تخصیص حافظه‌ی اضافی، برای رشته‌های تکراری صورت نمی‌گیرد. نمونه‌ی دیگر آن نمایش حرف "a" در یک ادیتور یا نمایشگر است. زمانیکه یک شیء Immutable حاوی اطلاعات حرف "a"، ایجاد شود، به سادگی می‌توان این تک وهله را جهت نمایش هزاران حرف "a" مورد استفاده‌ی مجدد قرار داد، بدون اینکه نگران مصرف حافظه‌ی بالای برنامه باشیم.
- کار با اشیاء Immutable به باگ‌های کمتری ختم می‌شود؛ چون همواره امکان تغییر حالت درونی یک شیء، توسط قسمت‌های مختلف برنامه، می‌تواند به باگ‌های ناخواسته‌ای منتهی شوند.
- Hash list‌ها که در جهت بهبود کارآیی برنامه‌ها بسیار مورد استفاده قرار می‌گیرند، بر اساس کلیدهایی Immutable قابل تشکیل هستند.


روش تعریف نوع‌های جدید record

کلاس ساده‌ی زیر را در نظر بگیرید:
public class User
{
   public string Name { set; get; }
}
برای تبدیل آن به یک نوع جدید record فقط کافی است واژه‌ی کلیدی class آن‌را با record جایگزین کنیم (به آن nominal record هم می‌گویند):
public record User
{
   public string Name { set; get; }
}
نحوه‌ی کار با آن و وهله سازی آن نیز دقیقا مانند کلاس‌ها است:
var user = new User();
user.Name = "User 1";
و ... در اینجا امکان انتساب مقداری به خاصیت Name وجود دارد؛ یعنی این خاصیت به صورت پیش‌فرض Immutable نیست.

روش تعریف دومی نیز در اینجا میسر است (به آن positional record هم می‌گویند):
public record User(string Name);
با این‌کار، به صورت خودکار یک record جدید تشکیل می‌شود که به همراه خاصیت Name است؛ چیزی شبیه به record قبلی که تعریف کردیم (به همین جهت نیاز است نام آن‌را شروع شده‌ی با حروف بزرگ درنظر بگیریم). با این تفاوت که این record، اینبار دارای سازنده است و همچنین خاصیت Name آن از نوع init-only است. در این حالت است که کل record به صورت immutable معرفی می‌شود؛ وگرنه روش تعریف یک خاصیت معمولی که از نوع init-only نیست (مانند مثال اول)، سبب بروز Immutability نخواهد شد.

برای کار با رکورد دومی که تعریف کردیم باید سازند‌ه‌ی این record را مقدار دهی کرد:
var user = new User("User 1");
// Error: Init-only property or indexer 'User.Name' can only be assigned
// in an object initializer, or on 'this' or 'base' in an instance constructor
// or an 'init' accessor. [CS9Features]csharp(CS8852)
user.Name = "User 1";
و همانطور که ملاحظه می‌کنید، چون خاصیت Name از نوع init-only است و در سازنده‌ی record تعریف شده مقدار دهی شده‌است، دیگر نمی‌توان آن‌را مقدار دهی مجدد کرد. همچنین در اینجا امکان استفاده‌ی از object initializers مانند new User { Name = "User 1" } نیز وجود ندارد؛ چون به همراه یک سازنده‌ی به صورت خودکار تولید شده‌است که خاصیتی init-only را مقدار دهی کرده‌است.


نوع جدید record چه اطلاعاتی را به صورت خودکار تولید می‌کند؟

روش دوم تعریف recordها اگر در نظر بگیریم:
public record User(string Name);
و در این حالت برنامه را کامپایل کنیم، به کدهای زیر که حاصل از دی‌کامپایل است، می‌رسیم:
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;
using CS9Features;

public class User : IEquatable<User>
{
 protected virtual Type EqualityContract
 {
  [System.Runtime.CompilerServices.NullableContext(1)]
  [CompilerGenerated]
  get
  {
   return typeof(User);
  }
 }

 public string Name
 {
  get;
  set/*init*/;
 }

 public User(string Name)
 {
  this.Name = Name;
  base..ctor();
 }

 public override string ToString()
 {
  StringBuilder stringBuilder = new StringBuilder();
  stringBuilder.Append("User");
  stringBuilder.Append(" { ");
  if (PrintMembers(stringBuilder))
  {
   stringBuilder.Append(" ");
  }
  stringBuilder.Append("}");
  return stringBuilder.ToString();
 }

 protected virtual bool PrintMembers(StringBuilder builder)
 {
  builder.Append("Name");
  builder.Append(" = ");
  builder.Append((object?)Name);
  return true;
 }

 [System.Runtime.CompilerServices.NullableContext(2)]
 public static bool operator !=(User? r1, User? r2)
 {
  return !(r1 == r2);
 }

 [System.Runtime.CompilerServices.NullableContext(2)]
 public static bool operator ==(User? r1, User? r2)
 {
  return (object)r1 == r2 || (r1?.Equals(r2) ?? false);
 }

 public override int GetHashCode()
 {
  return EqualityComparer<Type>.Default.GetHashCode(EqualityContract) * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Name);
 }

 public override bool Equals(object? obj)
 {
  return Equals(obj as User);
 }

 public virtual bool Equals(User? other)
 {
  return (object)other != null && EqualityContract == other!.EqualityContract && EqualityComparer<string>.Default.Equals(Name, other!.Name);
 }

 public virtual User <Clone>$()
 {
  return new User(this);
 }

 protected User(User original)
 {
  Name = original.Name;
 }

 public void Deconstruct(out string Name)
 {
  Name = this.Name;
 }
}
این خروجی به صورت خودکار تولید شده‌ی توسط کامپایلر، چنین نکاتی را به همراه دارد:
- record‌ها هنوز هم در اصل همان class‌های استاندارد #C هستند (یعنی در اصل reference type هستند).
- این کلاس به همراه یک سازنده و یک خاصیت init-only است (بر اساس تعاریف ما).
- متد ToString آن بازنویسی شده‌است تا اگر آن‌را بر روی شیء حاصل، فراخوانی کردیم، به صورت خودکار نمایش زیبایی را از محتوای آن ارائه دهد.
- این کلاس از نوع  <IEquatable<User است که امکان مقایسه‌ی اشیاء record را به سادگی میسر می‌کند. برای این منظور متدهای GetHashCode و Equals آن به صورت خودکار بازنویسی و تکمیل شده‌اند (یعنی مقایسه‌ی آن شبیه به value-type است).
- این کلاس امکان clone کردن اطلاعات جاری را مهیا می‌کند.
- همچنین به همراه یک متد Deconstruct هم هست که جهت انتساب خواص تعریف شده‌ی در آن، به یک tuple مفید است.

بنابراین یک رکورد به همراه قابلیت‌هایی است که سال‌ها در زبان #C وجود داشته‌اند و شاید ما به سادگی حاضر به تشکیل و تکمیل آن‌ها نمی‌شدیم؛ اما اکنون کامپایلر زحمت کدنویسی خودکار آن‌ها را متقبل می‌شود!


ساخت یک وهله‌ی جدید از یک record با clone کردن آن

اگر به کدهای حاصل از دی‌کامپایل فوق دقت کنید، یک قسمت جدید clone هم با syntax خاصی در آن ظاهر شده‌است:
public virtual User <Clone>$()
{
  return new User(this);
}
زمانیکه یک شیء Immutable است، دیگر نمی‌توان مقادیر خواص آن‌را در ادامه تغییر داد. اما اگر نیاز به اینکار وجود داشت، باید چکار کنیم؟ در C# 9.0 برای ایجاد وهله‌ی جدید معادلی از یک record، واژه‌ی کلیدی جدیدی را به نام with، اضافه کرده‌اند. برای نمونه اگر record زیر را در نظر بگیریم که دارای دو خاصیت نام و سن است:
public record User(string Name, int Age);
وهله سازی متداول آن به صورت زیر خواهد بود:
var user1 = new User("User 1", 21);
اما اگر خواستیم خاصیت سن آن‌را تغییر دهیم، می‌توان با استفاده از واژه‌ی کلیدی with، به صورت زیر عمل کرد:
var user2 = user1 with { Age = 31 };
کاری که در اصل در اینجا انجام می‌شود، ابتدا clone کردن شیء user1 است (یعنی دقیقا یک وهله‌ی جدید از user1 را با تمام اطلاعات قبلی آن در اختیار ما قرار می‌دهد که این وهله، ارجاعی را به شیء قبلی ندارد و از آن منقطع است). بنابراین نام user2، دقیقا همان "User 1" است که پیشتر تنظیم کردیم؛ با این تفاوت که اینبار مقدار سن آن متفاوت است. با استفاده از cloning، هنوز شیء user1 که immutable است، دست نخورده باقی مانده‌است و توسط with می‌توان خواص آن‌را تغییر داد و حاصل کار، یک شیء کاملا جدید است که مکان آن در حافظه، با مکان شیء user1 در حافظه، یکی نیست.


مقایسه‌ی نوع‌های record

در کدهای حاصل از دی‌کامپایل فوق، قسمت عمده‌ای از آن به تکمیل اینترفیس <IEquatable<User پرداخته شده بود. به همین جهت اکنون دو رکورد با مقادیر خواص یکسانی را ایجاد می‌کنیم:
var user1 = new User("User 1", 21);
var user2 = new User("User 1", 21);
سپس یکبار آن‌ها را از طریق عملگر == و بار دیگر به کمک متد Equals، مقایسه می‌کنیم:
Console.WriteLine("user1.Equals(user2) -> {0}", user1.Equals(user2));
Console.WriteLine("user1 == user2 -> {0}", user1 == user2);
خروجی هر دو حالت، True است:
user1.Equals(user2) -> True
user1 == user2 -> True
این مورد، یکی از مهم‌ترین تفاوت‌های recordها با classها هستند.
- زمانیکه عملگر == را بر روی شیء user1 و user2 اعمال می‌کنیم، اگر User، از نوع کلاس معمولی باشد، حاصل آن false خواهد بود؛ چون این دو، به یک مکان از حافظه اشاره نمی‌کنند، حتی با اینکه مقادیر خواص هر دو شیء یکی است.
- اما اگر به قطعه کد دی‌کامپایل شده دقت کنید، در یک رکورد که هر چند در اصل یک کلاس است، حتی عملگر == نیز بازنویسی شده‌است تا در پشت صحنه همان متد Equals را فراخوانی کند و این متد با توجه به پیاده سازی اینترفیس <IEquatable<User، اینبار دقیقا مقادیر خواص رکورد را یک به یک مقایسه کرده و نتیجه‌ی حاصل را باز می‌گرداند:
public virtual bool Equals(User? other)
{
   return (object)other != null &&
 EqualityContract == other!.EqualityContract &&
 EqualityComparer<string>.Default.Equals(Name, other!.Name) && 
EqualityComparer<int>.Default.Equals(Age, other!.Age);
}
این متدی است که به صورت خودکار توسط کامپایلر جهت مقایسه‌ی مقادیر خواص رکورد جدید تعریف شده، تشکیل شده‌است. به عبارتی recordها از لحاظ مقایسه، شبیه به value objects عمل می‌کنند؛ هرچند در اصل یک کلاس هستند.

یک نکته: بازنویسی عملگر == در SDK نگارش rc2 فعلی رخ‌داده‌است و در نگارش‌های قبلی preview، اینگونه نبود.


امکان ارث‌بری در recordها

دو رکورد زیر را در نظر بگیرید که اولی به همراه Name است و نمونه‌ی مشتق شده‌ی از آن، خاصیت init-only سن را نیز به همراه دارد:
    public record User
    {
        public string Name { get; init; }

        public User(string name)
        {
            Name = name;
        }
    }

    public record UserWithAge : User
    {
        public int Age { get; init; }

        public UserWithAge(string name, int age) : base(name)
        {
            Age = age;
        }
    }
در اینجا روش دیگر تعریف recordها را ملاحظه می‌کنید که شبیه به کلاس‌ها است و خواص آن init-only هستند. در این حالت اگر مقایسه‌ی زیر را انجام دهیم:
var user1 = new User("User 1");
var user2 = new UserWithAge("User 1", 21);

Console.WriteLine("user1.Equals(user2) -> {0}", user1.Equals(user2));
Console.WriteLine("user1 == user2 -> {0}", user1 == user2);
به خروجی زیر خواهیم رسید:
user1.Equals(user2) -> False
user1 == user2 -> False
علت آن را هم پیشتر بررسی کردیم. تساوی رکوردها بر اساس مقایسه‌ی مقدار تک تک خواص آن‌ها صورت می‌گیرد و چون user1 به همراه سن نیست، مقایسه‌ی این دو، false را بر می‌گرداند.

امکان تعریف ارث‌بری رکوردها به صورت زیر نیز وجود دارد و الزاما نیازی به روش تعریف کلاس مانند آن‌ها، مانند مثال فوق نیست:
public abstract record Food(int Calories);
public record Milk(int C, double FatPercentage) : Food(C);


رکوردها متد ToString را بازنویسی می‌کنند

در مثال قبلی اگر یک ToString را بر روی اشیاء تشکیل شده فراخوانی کنیم:
Console.WriteLine(user1.ToString());
Console.WriteLine(user2.ToString());
به این خروجی‌ها می‌رسیم:
User { Name = User 1 }
UserWithAge { Name = User 1, Age = 21 }
که حاصل بازنویسی خودکار متد ToString در پشت صحنه است.


امکان استفاده‌ی از Deconstruct در رکوردها

دو روش برای تعریف رکوردها وجود دارند؛ یکی شبیه به تعریف کلاس‌ها است و دیگری تعریف یک سطری، که positional record نیز نامیده می‌شود:
public record Person(string Name, int Age);
 فقط در حالت تعریف یک سطری positional record فوق است که خروجی خودکار نهایی تولیدی، به همراه public void Deconstruct نیز خواهد بود:
public void Deconstruct(out string Name, out int Age)
{
  Name = this.Name;
  Age = this.Age;
}
در این حالت می‌توان از tuples نیز برای کار با آن استفاده کرد:
var (name, age) = new Person("User 1", 21);
واژه‌ی «positional» نیز دقیقا به همین قابلیت اشاره می‌کند که بر اساس موقعیت خواص تعریف شده‌ی در رکورد، امکان Deconstruct آن‌ها به متغیرهای یک tuple وجود دارد. حالت تعریف کلاس مانند رکوردها، nominal نام دارد.


امکان استفاده‌ی از نوع‌های record در ASP.NET Core 5x

سیستم model binding در ASP.NET Core 5x، از نوع‌های record نیز پشتیبانی می‌کند؛ یک مثال:
 public record Person([Required] string Name, [Range(0, 150)] int Age);

 public class PersonController
 {
   public IActionResult Index() => View();

   [HttpPost]
   public IActionResult Index(Person person)
   {
    // ...
   }
 }


پرسش و پاسخ

آیا نوع‌های record به صورت value type معرفی می‌شوند؟
پاسخ: خیر. رکوردها در اصل reference type هستند؛ اما از لحاظ مقایسه، شبیه به value types عمل می‌کنند.

آیا می‌توان در یک کلاس، خاصیتی از نوع رکورد را تعریف کرد؟
پاسخ: بله. از این لحاظ محدودیتی وجود ندارد.

آیا می‌توان در رکوردها، از struct و یا کلاس‌ها جهت تعریف خواص استفاده کرد؟
پاسخ: بله. از این لحاظ محدودیتی وجود ندارد.

آیا می‌توان از واژه‌ی کلیدی with با کلاس‌ها و یا structها استفاده کرد؟
پاسخ: خیر. این واژه‌ی کلیدی در C# 9.0 مختص به رکوردها است.

آیا رکوردها به صورت پیش‌فرض Immutable هستند؟
پاسخ: اگر آن‌ها را به صورت positional records تعریف کنید، بله. چون در این حالت خواص تشکیل شده‌ی توسط آن‌ها از نوع init-only هستند. در غیراینصورت، می‌توان خواص غیر init-only را نیز به تعریف رکوردها اضافه کرد.