واژهی کلیدی جدید required در C# 11.0، همانند خواص
init-only که پیشتر معرفی شدند، با هدف آغاز و نمونه سازی دقیقتر و سادهتر اشیایی است که برای اینکار، به تعاریف ویژهی سازندهی کلاسها وابسته نیستند.
امکان نمونه سازی بدون قید و شرط کلاسها
تعریف کلاس Article1 را به صورت زیر درنظر بگیرید:
public class Article1
{
public string Title { get; set; }
public string? Subtitle { get; set; }
public string Author { get; set; }
public DateTime Published { get; set; }
}
ساختار پروژههای دات نت 7 نیز به صورت پیشفرض به صورت زیر است:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
یعنی
nullable reference types در آنها فعال است. با این فعال بودن، به اخطارهای زیر میرسیم:
Non-nullable property 'Title' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [CS11Tests]csharp(CS8618)
Non-nullable property 'Author' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [CS11Tests]csharp(CS8618)
عنوان میکند که خاصیتهای Title و Author، به صورت غیرنالپذیر تعریف شدهاند (و همانند Subtitle نالپذیر نیستند)؛ اما تعریف این کلاس به نحوی است که این مساله را الزامی نمیکند. یعنی میتوان نمونهای از Article1 را ایجاد کرد که در آن، هر دوی این خواص نال باشند؛ هرچند در این حالت مشکلی از لحاظ کامپایل وجود نخواهد داشت، اما ممکن است به علت اشتباه استفادهی از آنها، به null reference exceptions برسیم. چون یکی از مهمترین اهداف استفاده از یک چنین تعاریفی و فعال سازی nullable reference type در یک پروژه، ارائهی متادیتای بهتری جهت خواص و پارامترها و خروجیهای متدهاست تا استفاده کننده دقیقا بداند که آیا این خواص میتوانند نال باشد یا خیر. اگر public string ای تعریف شده، یعنی این خاصیت قطعا نال نخواهد بود و میتوان بدون مشکل و بدون بررسی مقدار آن، از آن استفاده کرد و اگر ?public string ای تعریف شده، یعنی ممکن است مقدار آن نال نیز باشد و بهتر است پیش از استفادهی از آن، حتما مقدار آن بررسی شود. اکنون مشکل اینجا است که هیچگونه قیدی، جهت اجبار به مقدار دهی خواص غیرنال پذیر در اینجا وجود ندارند و میتوان نمونهای از شیء Article1 را ایجاد کرد که در آن متادیتای خواص غیرنال پذیر تعریفی در آن، نقض شوند.
مدیریت کردن نحوهی نمونه سازی کلاسها، با وابستگی به سازندههای آن
یکی از روشهای مدیریت مشکلی که تا اینجا بررسی شد، تعریف سازندههای متعددی برای یک کلاس است؛ تا توسط آن بتوان مقدار دهی یک سری از خواص را اجباری کرد:
public class Article2
{
public Article2(string title, string subtitle, string author, DateTime published)
{
Title = title;
Subtitle = subtitle;
Author = author;
Published = published;
}
public Article2(string title, string author, DateTime published)
{
Title = title;
Author = author;
Published = published;
}
public string Title { get; set; }
public string? Subtitle { get; set; }
public string Author { get; set; }
public DateTime Published { get; set; }
}
در این کلاس، نمونهی بهبود یافتهی Article1 را مشاهده میکنید که استفاده کننده را وادار به مقدار دهی title و author میکند. در این حالت اخطارهای کامپایلری را که مشاهده کردید، رفع میشوند؛ اما به همراه این مسایل است:
- تعداد سطرهای تعریف این کلاس، به شدت افزایش یافتهاست.
- با اضافه شدن خواص بیشتری به کلاس، به تعاریف بیشتری نیاز خواهد بود.
- سازندهها کار خاصی را بجز نگاشت مقادیر ارائه شده، به خواص کلاس، انجام نمیدهند.
- نمونه سازی این کلاسها، شکل طولانی و غیرواضح زیر را پیدا میکند و زیبایی
inline object initializers را ندارند:
Article2 article = new("C# 11 Required Keyword", "A new language feature", "Name", new DateTime(2022, 11, 12));
البته روش دیگر مدیریت یک چنین اخطارهایی، استفاده از مقدار ویژهی !default است که سبب محو اخطارهای یاد شده میشود؛ اما باز هم مقدار دهی آنرا الزامی نمیکند. فقط به این معنا است که قول میدهیم این خاصیت را در جای دیگری مقدار دهی کنیم و هیچگاه نال نباشد!
public string Title { get; set; } = default!;
مدیریت کردن نحوهی نمونه سازی کلاسها، بدون وابستگی به سازندههای آن در C# 11.0
C# 11 به همراه واژهی کلیدی جدیدی به نام required است تا دیگر نیازی نباشد همانند راه حل فوق، سازندههای متعددی را جهت اجبار به مقدار دهی خواص یک شیء، تعریف کنیم. در این حالت تعریف کلاس Article به صورت زیر خلاصه میشود و دیگر به همراه اخطارهای کامپایلر نمایش داده شده نیز نیست:
public class Article3
{
public required string Title { get; set; }
public string? Subtitle { get; set; }
public required string Author { get; set; }
public DateTime Published { get; set; }
}
به این ترتیب هنوز میتوان از زیبایی و خوانایی به همراه نمونه سازی توسط روش inline object initializers بهرهمند شد و همچنین مطمئن بود که اگر استفاده کننده خاصیت غیرنالپذیر Title را مقدار دهی نکند، اینبار با یک خطای کامپایلر متوقف خواهد شد:
معرفی ویژگی جدید SetsRequiredMembers
کلاس Book زیر را که به همراه یک خاصیت required و دو سازندهاست، درنظر بگیرید:
public class Book
{
public Book() => Name = string.Empty;
public Book(string name) => Name = name;
public required string Name { get; set; }
}
اکنون فرض کنید که بر این اساس، شیءای را به صورت زیر نمونه سازی کردهایم:
Book book = new("Book's Name");
این قطعه کد با خطای زیر کامپایل نمیشود:
Required member 'Book.Name' must be set in the object initializer or attribute constructor. [CS11Tests]csharp(CS9035)
عنوان میکند که باید خاصیت Name را حتما مقدار دهی کرد؛ چون از نوع required است. هرچند سازندهای که از آن استفاده شده، این مقدار دهی را انجام دادهاست و مشکلی از لحاظ عدم مقدار دهی خاصیت Name در اینجا وجود ندارد. برای رفع این مشکل، باید تغییر زیر را اعمال کرد:
public class Book
{
[SetsRequiredMembers]
public Book() => Name = string.Empty;
[SetsRequiredMembers]
public Book(string name) => Name = name;
public required string Name { get; set; }
}
با استفاده از ویژگی جدید SetsRequiredMembers عنوان میکنیم که این سازندهی خاص، حتما خواص از نوع required را نیز مقدار دهی میکند و نیازی به صدور خطای یاد شده نیست. در این حالت بررسی خواص required توسط کامپایلر غیرفعال میشود.
محدودیتهای کار با خواص required
- واژهی کلیدی required را میتوان تنها به خواص و فیلدهای نوعهای class, record, record struct اعمال کرد. امکان اعمال این واژهی کلیدی به اجزای یک اینترفیس وجود ندارد.
- میدان دید اعضای required باید حداقل در حد نوعهای دربرگیرندهی آنها باشند. برای مثال اگر کلاسی public است، نمیتوان در آن یک فیلد required با میدان دید protected را تعریف کرد.
- نوعهای مشتق شدهی از یک نوع پایه، نمیتوانند اعضای required آنرا مخفی کنند و اگر قصد بازنویسی آنرا دارند، باید حتما واژهی کلیدی required را لحاظ کنند.
- اگر سازندهای به سازندهی دیگری از طریق ذکر ()base و یا ()this زنجیر شده باشد نیز باید ویژگی SetsRequiredMembers مرتبط را تکرار کند.