داتنت 8 به همراه بهبودهای قابل ملاحظهای در کارآیی برنامههای داتنتی است و در این بین تعدادی قابلیت جدید را نیز به زبان سیشارپ اضافه کردهاست. در این مطلب ویژگی جدید «Alias any type» آنرا بررسی میکنیم. پیشنیاز کار با این قابلیت تنها نصب SDK داتنت 8 است.
امکان تعریف alias، قابلیت جدیدی نیست!
در نگارشهای پیشین زبان #C نیز میتوان برای نوعهای نامدار داتنت، alias/«نام مستعار» تعریف کرد؛ برای مثال:
using MyConsole = System.Console;
MyConsole.WriteLine("Test console");
Aliasها در قسمت using تعاریف یک کلاس معرفی میشوند و یکی از اهدف آنها، کوتاه کردن تعاریف فضاهای نام طولانی است و یا رفع تداخلها؛ همچنین تنها به Named types، محدود هستند و Named types فقط شامل این موارد میشوند: classes ،delegates ،interfaces ،records و structs
بنابراین دو حالت تعریف Namespace alias برای کوتاه سازی فضاهای نام طولانی و یا تعریف Type alias برای معرفی یک نام مستعار جدید برای نوعی مشخص، میسر است:
// Namespace alias
using SuperJSON = System.Text.Json;
var document = SuperJSON.JsonSerializer.Serialize("{}");
// Type alias
using SuperJSON = System.Text.Json.JsonSerializer;
var document = SuperJSON.Serialize("{}");
تنها کارکرد نامهای مستعار، کوتاه و زیبا سازی نامهای طولانی نیستند. برای مثال گاهی از اوقات ممکن است که بین نام نوعهای موجود در usingهای جاری، تداخل حاصل شود و برنامه کامپایل نشود. برای مثال فرض کنید که دو using زیر را تعریف کردهاید:
using UnityEngine;
using System;
Random rnd = new Random();
کامپایل این برنامه میسر نیست. چون هر دو نوع System.Random و UnityEngine.Random پیشتر تعریف شدهاند و در اینجا دقیقا مشخص نیست که تامین کنندهی شیء Random، کدام فضای نام است. در این حالت میتوان برای مثال در حین نمونه سازی، فضای نام را صراحتا ذکر کرد:
var rnd = new System.Random();
و یا میتوان برای آن نام مستعاری نیز تعریف کرد:
using Random = System.Random;
در این حالت دیگر تداخلی وجود نداشته و کامپایلر دقیقا میداند که تامین کنندهی Random، کدام کتابخانه و کدام فضای نام است.
امکان تعریف alias برای هر نوعی در C# 12.0
محدودیت امکان تعریف alias برای نوعهای نامدار داتنت در C# 12.0 برطرف شده و اکنون میتوان برای انواع و اقسام نوعها مانند آرایهها، tuples و غیره نیز alias تعریف کرد:
using Ints = int[];
using DatabaseInt = int?;
using OptionalFloat = float?;
using Grade= decimal;
using Point3D = (int, int, int);
using Person = (string name, int age, string country);
using unsafe P = char*;
using Matrix = int[][];
Matrix aMatrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
در اینجا امکان تعریف alias را برای آرایهها، nullable value types، نوعهای توکار، tupleها و حتی نوعهای unsafe، مشاهده میکنید.
یک نکته: امکان تعریف alias برای nullable reverence types وجود ندارد.
بررسی یک مثال C# 12.0
در اینجا محتویات یک فایل Program.cs یک برنامهی کنسول داتنت 8 را مشاهده میکنید:
using MyConsole = System.Console;
using Person = (string name, int age, string country);
Person person = new("User 1", 33, "Iran");
Console.WriteLine(person);
PrintPerson(person);
MyConsole.WriteLine("Test console");
static void PrintPerson(Person person)
{
MyConsole.WriteLine($"{person.name}, {person.age}, {person.country}");
}
در این مثال برای یک نوع tuple سفارشی، یک alias به نام Person تعریف شده و سپس از آن برای نمونه سازی یک شیء جدید و یا ارسال آن به عنوان یک پارامتر متد، استفاده شدهاست. خروجی برنامهی فوق به صورت زیر است:
(User 1, 33, Iran)
User 1, 33, Iran
Test console
چه زمانی بهتر است از قابلیت تعریف نامهای مستعار نوعها و یا فضاهای نام استفاده شود؟
اگر یک نام طولانی را بتوان به این صورت خلاصه کرد، مفید هستند؛ برای مثال ساده سازی تعریف یک لیست طولانی به صورت زیر:
using Companies = System.Collections.Generic.List<Company>;
Companies GetCompanies()
{
// logic here
}
class Company
{
public string Name;
public int Id;
}
و مثالی دیگر در این زمینه، کوتاه سازی تعاریف متداول جنریک طولانی است:
using EventHandlers = System.Collections.Generic.IEnumerable<System.Func<System.Threading.Tasks.Task>>;
و یا اگر بتوانند رفع تداخلی را حاصل کنند، بکارگیری آنها ضروری است (مانند مثال شیء Random ابتدای بحث) و یا اگر بتوانند از تکرار تعریف یک tuple جلوگیری کنند، ذکر آنها یک refactoring مثبت بهشمار میرود؛ مانند مثال زیر که در آن از تعریف نوع tuple ای، دوبار استفاده شدهاست:
using Country = (string Abbreviation, string Name);
Country GetCountry(string abbreviation)
{
// Logic here
}
List<Country> GetCountries()
{
// Logic here
}
اما ... آیا واقعا تعاریفی مانند ذیل، مفید یا ضروری هستند؟
using Ints = int[];
using DatabaseInt = int?;
using OptionalFloat = float?;
اینجا فقط قطعه کدی اضافی را که بیشتر سبب سردرگمی و بالا بردن درجهی پیچیدگی برنامه میشود، تولید کردهایم. خوانایی و سادگی درک برنامه در این حالت کاهش پیدا میکند.
میدان دید نامهای مستعار
به صورت پیشفرض، تمام نامهای مستعار تنها در داخل همان فایلی که تعریف شدهاند، قابل استفاده میباشند.
از زمان C# 10.0 ، میتوان پیش از واژهی کلیدی using از واژهی کلیدی global نیز استفاده کرد تا تعریف آنها فقط در پروژهی جاری به صورت سراسری قابل دسترسی شود.
به همین جهت اگر نوعی قرار است در سایر پروژهها استفاده شود، بهتر است از global using استفاده نشده و از همان روشهای متداول تعریف records و یا classes استفاده شود.