نظرات مطالب
پیاده سازی Remote Validation در Blazor
یک نکتهی تکمیلی: امکان اجرای سادهتر اعمال async پس از رخداد onchange در Blazor 7x
پیشنیاز: برای اجرای نکات زیر، نیاز به حداقل NET SDK 7.0.101. است و اگر از ویژوال استودیو استفاده میکنید، باید شماره نگارش آن حداقل 17.4.3 باشد؛ در غیراینصورت با خطای «'cannot convert from 'method group' to 'Action» مواجه خواهید شد.
همانطور که در مطلب فوق هم مشاهده کردید، در جهت انجام اعتبارسنجی از راه دور async پس از ورود اطلاعات، تنها رخدادی که در اینجا در اختیار ما قرار میگیرد، رخداد submit (در حالت موفقیت اعتبارسنجی سمت کلاینت و یا تنها submit معمولی) است. بنابراین برای دسترسی به رخدادهای بیشتر EditForm، نیاز است با EditContext آن کار کنیم تا بتوانیم برای مثال به کمک رویداد OnFieldChanged آن، این عملیات async را انجام دهیم. در دات نت 7.0.1، این وضعیت با معرفی modifier جدیدی به نام bind:after@ تغییر کردهاست که در ادامه توضیحات آنرا ملاحظه خواهید کرد.
تعاریف زیر را جهت پیاده سازی یک انقیاد دوطرفه (two-way data-binding) درنظر بگیرید:
که در اولی با درج bind@ بر روی یک المان استاندارد HTML و در دومی با ذکر bind-Value@ میسر شدهاست. در این حالت هر تغییری در مقدار کنترل قرار گرفتهی بر روی صفحه، به خاصیت متصل به آن منعکس میشود (با پیاده سازی خودکار یک رویدادگردان onchange توسط Blazor در پشت صحنه) و برعکس.
مشکل! اگر در اینجا نیاز باشد تا در حین ورود اطلاعات، کدی نیز اجرا شود چه باید کرد؟
متاسفانه در این حالت نمیتوانیم رویدادگردان onchange را به صورت دستی، به تعاریف فوق اضافه کنیم و اگر چنین کاری را انجام دهیم، با خطای زیر مواجه خواهیم شد:
عنوان میکند که چون ما خودمان onchange را راسا پیاده سازی کردهایم، شما دیگر نمیتوانید اینکار را مجددا انجام دهید!
راه حلهای ممکن انجام اعمال async پس از بروز تغییرات تا پیش از دات نت 7
الف) username متصل را تبدیل به یک خاصیت get و set دار کرده و اکنون در قسمت set آن میتوان عملیات synchronous ای را انجام داد که متاسفانه در این حالت، امکان انجام اعمال async میسر نیست.
ب) چون میخواهیم عملیات async ای را پس از تغییرات انجام دهیم، باید از انقیاد دوطرفه صرفنظر کنیم و مدیریت رویداد onchange را خودمان بهدست بگیریم؛ برای نمونه در مثال زیر میتوان با پیاده سازی async متد CheckUsername به هدف خود رسید؛ اما همانطور که مشاهده میکنید، این عملیات اکنون one-way binding است:
ج) اگر از EditForm و کنترلهای آن استفاده میکنیم، میتوان همانند مثال مطلب جاری از رویداد OnFieldChanged استفاده کرد یا راه دیگر آن شکستن bind-Value@ به اجزای تشکیل دهندهی آن است که سه جزء Value ،ValueExpression و ValueChanged را تشکیل میدهد و اینبار میتوان رویداد ValueChanged آنرا دستی پیاده سازی کرد:
راه حل جدید انجام اعمال async پس از بروز تغییرات در دات نت 7
Blazor در دات نت 7، به همراه یک bind:after modifier@ است که امکان اجرای متدی را (چه همزمان یا غیرهمزمان) پس از بروز تغییرات، میسر میکند و مزیت آن عدم نیاز به بازنویسی متد onchange و از دست دادن انقیاد دوطرفه است:
همانطور که مشاهده میکنید هنوز در این حالت bind@ وجود دارد (یعنی two-way data-binding هنوز هم برقرار است) و توسط bind:after@، متدی را که قرار است پس از تغییرات اجرا شود، مشخص کردهایم.
این modifier را حتی میتوان به کنترلهای EditForm نیز اعمال کرد؛ بدون اینکه نیازی به استفاده از راهحلهای پیشین (حالت ج عنوان شده) باشد:
در اینجا نیز هنوز از مزایای two-way data-binding برخورداریم و همچنین میتوانیم پس از تغییری، یک متد sync و یا async را فراخوانی کنیم. برای نمونه پیاده سازی اعتبارسنجی از راه دور مطلب جاری، اینبار به صورت زیر ساده میشود:
پیشنیاز: برای اجرای نکات زیر، نیاز به حداقل NET SDK 7.0.101. است و اگر از ویژوال استودیو استفاده میکنید، باید شماره نگارش آن حداقل 17.4.3 باشد؛ در غیراینصورت با خطای «'cannot convert from 'method group' to 'Action» مواجه خواهید شد.
همانطور که در مطلب فوق هم مشاهده کردید، در جهت انجام اعتبارسنجی از راه دور async پس از ورود اطلاعات، تنها رخدادی که در اینجا در اختیار ما قرار میگیرد، رخداد submit (در حالت موفقیت اعتبارسنجی سمت کلاینت و یا تنها submit معمولی) است. بنابراین برای دسترسی به رخدادهای بیشتر EditForm، نیاز است با EditContext آن کار کنیم تا بتوانیم برای مثال به کمک رویداد OnFieldChanged آن، این عملیات async را انجام دهیم. در دات نت 7.0.1، این وضعیت با معرفی modifier جدیدی به نام bind:after@ تغییر کردهاست که در ادامه توضیحات آنرا ملاحظه خواهید کرد.
تعاریف زیر را جهت پیاده سازی یک انقیاد دوطرفه (two-way data-binding) درنظر بگیرید:
<input @bind="username" /> <InputText @bind-Value="Model.Name" />
مشکل! اگر در اینجا نیاز باشد تا در حین ورود اطلاعات، کدی نیز اجرا شود چه باید کرد؟
متاسفانه در این حالت نمیتوانیم رویدادگردان onchange را به صورت دستی، به تعاریف فوق اضافه کنیم و اگر چنین کاری را انجام دهیم، با خطای زیر مواجه خواهیم شد:
RZ10008 The attribute 'onchange' is used two or more times for this element. Attributes must be unique (case-insensitive). The attribute 'onchange' is used by the '@bind' directive attribute.
راه حلهای ممکن انجام اعمال async پس از بروز تغییرات تا پیش از دات نت 7
الف) username متصل را تبدیل به یک خاصیت get و set دار کرده و اکنون در قسمت set آن میتوان عملیات synchronous ای را انجام داد که متاسفانه در این حالت، امکان انجام اعمال async میسر نیست.
ب) چون میخواهیم عملیات async ای را پس از تغییرات انجام دهیم، باید از انقیاد دوطرفه صرفنظر کنیم و مدیریت رویداد onchange را خودمان بهدست بگیریم؛ برای نمونه در مثال زیر میتوان با پیاده سازی async متد CheckUsername به هدف خود رسید؛ اما همانطور که مشاهده میکنید، این عملیات اکنون one-way binding است:
<input value="@username" @onchange="CheckUsername" />
<InputText Value="@Model.Name" ValueExpression="()=>Model.Name" ValueChanged="(string s)=>CheckUsername(s)" /> <ValidationMessage For="() => Model.Name" />
راه حل جدید انجام اعمال async پس از بروز تغییرات در دات نت 7
Blazor در دات نت 7، به همراه یک bind:after modifier@ است که امکان اجرای متدی را (چه همزمان یا غیرهمزمان) پس از بروز تغییرات، میسر میکند و مزیت آن عدم نیاز به بازنویسی متد onchange و از دست دادن انقیاد دوطرفه است:
<input @bind="username" @bind:after="CheckUsername" />
این modifier را حتی میتوان به کنترلهای EditForm نیز اعمال کرد؛ بدون اینکه نیازی به استفاده از راهحلهای پیشین (حالت ج عنوان شده) باشد:
<InputText @bind-Value="Model.Name" @bind-Value:after="CheckUsername" /> <ValidationMessage For="() => Model.Name" />
async Task CheckUsername() { if (!string.IsNullOrWhiteSpace(Model.Name)) { _messageStore?.Clear(EditContext.Field(nameof(UserDto.Name))); var response = await HttpClient.PostAsJsonAsync( UserValidationUrl, new UserDto { Name = Model.Name }); var responseContent = await response.Content.ReadAsStringAsync(); if (string.Equals(responseContent, "false", StringComparison.OrdinalIgnoreCase)) { _messageStore?.Add(EditContext.Field(nameof(UserDto.Name)), $"`{Model.Name}` is in use. Please choose another name."); } EditContext.NotifyValidationStateChanged(); } }
تولید برنامههای اجرایی تک فایلی در زمان NET Core 3x. ارائه شد؛ اما به همراه این مسائل نیز بود:
- فایل اجرایی تک فایلی تولید شده در اصل یک فایل zip خود باز شونده بود که در یک مکان موقتی به صورت خودکار باز و اجرا میشد. این حالت با آنتیویروسها و یا سیستمهایی که قسمتهای اصلی آنها جهت کاربران عادی قفل شدهاند، مشکلاتی را ایجاد میکرد.
- حجم فایل نهایی تولید شده قابل توجه بود. برای نمونه یک برنامهی کنسول Hello world آن حدود 70 مگابایت میشد. البته باید درنظر داشت که یک چنین خروجی به همراه یک NET Core runtime. کامل نیز میشد.
از آن زمان تغییرات تدریجی مفیدی در این زمینه رخ دادهاند که خلاصهای از آنها را تا دات نت 6 در ادامه مرور میکنیم.
اصول تولید برنامههای اجرایی تک فایلی دات نت
فرض کنید برنامهی کنسول ما از این سه سطر تشکیل شدهاست:
برای تولید یک برنامهی اجرایی تک فایلی بر اساس آن، میتوان دستور زیر را در خط فرمان اجرا کرد:
در این حالت ذکر سیستم عامل هدف، اجباری است؛ از این جهت که خروجی نهایی تنها برای یک سیستم عامل تهیه میشود.
پس از اجرای دستور فوق، اگر به مکان C:\MyProject\bin\Release\net6.0\win-x64\publish مراجعه کنیم، به یک فایل exe حدود 62 مگابایتی خواهیم رسید که کمی کم حجمتر از نمونهی NET Core 3x. آن است! البته همانطور که عنوان شد این خروجی به همراه runtime متناظری نیز هست. اگر بخواهیم این runtime را از آن حذف کنیم میتوان به صورت زیر عمل کرد:
با استفاده از سوئیچ self-contained false دیگر خروجی نهایی به همراه runtime دات نت تشکیل نخواهد شد و حجم حاصل تنها 150 کیلوبایت خواهد بود. در این حالت استفاده کنندهی نهایی باید runtime را خودش به صورت مجزایی نصب کند.
یک نکته: میتوان سوئیچهای فوق را به فایل csproj نیز به صورت زیر اضافه کرد:
تک فایلهای اجرایی دات نت 6 دیگر فایلهای zip خود باز شونده نیستند
همانطور که عنوان شد، تک فایلهای اجرایی تولید شدهی در نگارشهای پیشین دات نت، چیزی بجز یک فایل zip خود بازشونده که همه چیز داخل آن قرار گرفته بودند، نبودند. این حالت دیگر در دات نت 6 صادق نیست و اینبار خروجی نهایی در حافظه بارگذاری میشود و نیاز به باز شدن آن در مکانهای temp برطرف شدهاست. تا زمان دات نت 5، این قابلیت فقط برای خروجیهای لینوکس تدارک دیده شده بود، اما با ارائهی دات نت 6، خروجیهای ویندوز و مک هم فایلهای اجرایی واقعی هستند.
فعالسازی IL Trimming
به صورت پیشفرض با اجرای دستورات تولید تک فایلهای اجرایی برنامههای دات نت، تمام وابستگیهای استفاده شده بدون هیچگونه بهینه سازی در کنار هم قرار میگیرند. با فعالسازی قابلیت IL Trimming میتوان وابستگیهایی را که برنامه از آنها استفاده نمیکند، از خروجی نهایی حذف کرد که در نتیجهی آن، شاهد کاهش حجم قابل ملاحظهی فایل تولیدی نهایی خواهیم بود. برای اینکار میتوان سوئیچ PublishTrimmed را فعالسازی کرد:
پس از آن برنامهی 60 مگابایتی تولیدی در ابتدای بحث، تبدیل به یک برنامهی اجرایی تک فایلی 11 مگابایتی میشود که کاهش حجم قابل توجهی است.
باید دقت داشت که این حجم نهایی، یک فایل اجرایی واقعی بدون نیاز به نصب هیچ نوع runtime ای است و کاملا متکی به خود است.
فعالسازی فشرده سازی
به همراه دات نت 6، امکان فشرده سازی خودکار این خروجی نهایی تک فایلی، جهت کاهش هرچه بیشتر حجم آن نیز میسر شدهاست. برای اینکار میتوان سوئیچ EnableCompressionInSingleFile را فعالسازی کرد:
خروجی آن یک فایل 30 مگابایتی بدون IL Trimming است که نسبت به خروجی 60 مگابایتی ابتدای بحث، باز هم کاهش قابل ملاحظهای داشتهاست.
- فایل اجرایی تک فایلی تولید شده در اصل یک فایل zip خود باز شونده بود که در یک مکان موقتی به صورت خودکار باز و اجرا میشد. این حالت با آنتیویروسها و یا سیستمهایی که قسمتهای اصلی آنها جهت کاربران عادی قفل شدهاند، مشکلاتی را ایجاد میکرد.
- حجم فایل نهایی تولید شده قابل توجه بود. برای نمونه یک برنامهی کنسول Hello world آن حدود 70 مگابایت میشد. البته باید درنظر داشت که یک چنین خروجی به همراه یک NET Core runtime. کامل نیز میشد.
از آن زمان تغییرات تدریجی مفیدی در این زمینه رخ دادهاند که خلاصهای از آنها را تا دات نت 6 در ادامه مرور میکنیم.
اصول تولید برنامههای اجرایی تک فایلی دات نت
فرض کنید برنامهی کنسول ما از این سه سطر تشکیل شدهاست:
using System; Console.WriteLine("Hello, World!"); Console.ReadLine();
dotnet publish -p:PublishSingleFile=true -r win-x64 -c Release --self-contained true
پس از اجرای دستور فوق، اگر به مکان C:\MyProject\bin\Release\net6.0\win-x64\publish مراجعه کنیم، به یک فایل exe حدود 62 مگابایتی خواهیم رسید که کمی کم حجمتر از نمونهی NET Core 3x. آن است! البته همانطور که عنوان شد این خروجی به همراه runtime متناظری نیز هست. اگر بخواهیم این runtime را از آن حذف کنیم میتوان به صورت زیر عمل کرد:
dotnet publish -p:PublishSingleFile=true -r win-x64 -c Release --self-contained false
یک نکته: میتوان سوئیچهای فوق را به فایل csproj نیز به صورت زیر اضافه کرد:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net6.0</TargetFramework> <PublishSingleFile>true</PublishSingleFile> <SelfContained>true</SelfContained> <RuntimeIdentifier>win-x64</RuntimeIdentifier> <PublishReadyToRun>true</PublishReadyToRun> </PropertyGroup> </Project>
تک فایلهای اجرایی دات نت 6 دیگر فایلهای zip خود باز شونده نیستند
همانطور که عنوان شد، تک فایلهای اجرایی تولید شدهی در نگارشهای پیشین دات نت، چیزی بجز یک فایل zip خود بازشونده که همه چیز داخل آن قرار گرفته بودند، نبودند. این حالت دیگر در دات نت 6 صادق نیست و اینبار خروجی نهایی در حافظه بارگذاری میشود و نیاز به باز شدن آن در مکانهای temp برطرف شدهاست. تا زمان دات نت 5، این قابلیت فقط برای خروجیهای لینوکس تدارک دیده شده بود، اما با ارائهی دات نت 6، خروجیهای ویندوز و مک هم فایلهای اجرایی واقعی هستند.
فعالسازی IL Trimming
به صورت پیشفرض با اجرای دستورات تولید تک فایلهای اجرایی برنامههای دات نت، تمام وابستگیهای استفاده شده بدون هیچگونه بهینه سازی در کنار هم قرار میگیرند. با فعالسازی قابلیت IL Trimming میتوان وابستگیهایی را که برنامه از آنها استفاده نمیکند، از خروجی نهایی حذف کرد که در نتیجهی آن، شاهد کاهش حجم قابل ملاحظهی فایل تولیدی نهایی خواهیم بود. برای اینکار میتوان سوئیچ PublishTrimmed را فعالسازی کرد:
dotnet publish -p:PublishSingleFile=true -r win-x64 -c Release --self-contained true -p:PublishTrimmed=true
باید دقت داشت که این حجم نهایی، یک فایل اجرایی واقعی بدون نیاز به نصب هیچ نوع runtime ای است و کاملا متکی به خود است.
فعالسازی فشرده سازی
به همراه دات نت 6، امکان فشرده سازی خودکار این خروجی نهایی تک فایلی، جهت کاهش هرچه بیشتر حجم آن نیز میسر شدهاست. برای اینکار میتوان سوئیچ EnableCompressionInSingleFile را فعالسازی کرد:
dotnet publish -p:PublishSingleFile=true -r win-x64 -c Release --self-contained true -p:EnableCompressionInSingleFile=true
اشتراکها
دوره مقدماتی Microservices در دات نت
دات نت 7 به همراه دو متد جدید Order و OrderDescending است که مرتب سازی مجموعههای ساده را انجام میدهند.
روش متداول مرتب سازی مجموعههای ساده تا پیش از دات نت 7
فرض کنید لیستی از اعداد را داریم:
تا پیش از دات نت 7 با استفاده از متدهای OrderBy و OrderByDescending موجود به همراه LINQ، امکان مرتب سازی صعودی و نزولی این لیست وجود دارد:
که در اینجا ذکر پارامتر keySelector ضروری است:
هرچند میشد طراحی آن سادهتر باشد و حداقل برای مجموعههای ساده، نیازی به ذکر آن نباشد.
روش جدید مرتب سازی مجموعههای ساده در دات نت 7
دات نت 7 به همراه دو متد جدید Order و OrderDescending است که دیگر نیازی به ذکر پارامتر keySelector ذکر شده را ندارند:
و امضای آنها به صورت زیر است:
که در حقیقت دو متد الحاقی جدید قابل اعمال بر روی انواع و اقسام IEnumerableها هستند.
در مورد سایر مجموعههای پیچیده چطور؟
فرض کنید کلاس User را:
به همراه لیستی از آن تعریف کردهایم:
سؤال: آیا اگر متد Order را بر روی این لیست فراخوانی کنیم:
برای مثال این مجموعه بر اساس نام و سن مرتب خواهد شد؟ که پاسخ آن خیر است و همچنین استثنائی را صادر میکند بر این مبنا که باید کلاس User، اینترفیس IComparable را پیاده سازی کند تا بتوان آنها را مقایسه کرد؛ برای مثال چیزی شبیه به تغییرات زیر:
که در یک چنین مواردی شاید بهتر باشد از همان متد OrderBy پیشین استفاده کرد که الزامی به پیاده سازی اینترفیس IComparable را ندارد:
روش متداول مرتب سازی مجموعههای ساده تا پیش از دات نت 7
فرض کنید لیستی از اعداد را داریم:
var numbers = new List<int> { -7, 1, 5, -6 };
var sortedNumbers1 = numbers.OrderBy(n => n); var sortedNumbers2 = numbers.OrderByDescending(n => n);
public static IOrderedEnumerable<TSource> OrderBy<TSource,TKey>( [NotNull] this IEnumerable<TSource> source, [NotNull] Func<TSource,TKey> keySelector)
روش جدید مرتب سازی مجموعههای ساده در دات نت 7
دات نت 7 به همراه دو متد جدید Order و OrderDescending است که دیگر نیازی به ذکر پارامتر keySelector ذکر شده را ندارند:
var sortedNumbers3 = numbers.Order(); var sortedNumbers4 = numbers.OrderDescending();
public static IOrderedEnumerable<T> Order<T>(this IEnumerable<T> source) public static IOrderedEnumerable<T> OrderDescending<T>(this IEnumerable<T> source)
در مورد سایر مجموعههای پیچیده چطور؟
فرض کنید کلاس User را:
public class User { public string Name { set; get; } public int Age { set; get; } }
List<User> users = new() { new User { Name = "User 1", Age = 34 }, new User { Name = "User 2", Age = 24 }, };
var orderedUsers = users.Order();
public class User : IComparable<User> { public string Name { set; get; } public int Age { set; get; } public int CompareTo(User? other) { if (ReferenceEquals(this, other)) { return 0; } if (ReferenceEquals(null, other)) { return 1; } var nameComparison = string.Compare(Name, other.Name, StringComparison.Ordinal); if (nameComparison != 0) { return nameComparison; } return Age.CompareTo(other.Age); } }
var orderedUsers2 = users.OrderBy(user => user.Name).ThenBy(user => user.Age);
مسیرراهها
آزمون واحد در دات نت
آشنایی با آزمایش واحد (unit testing) در دات نت قسمت اول
آشنایی با آزمایش واحد (unit testing) در دات نت قسمت دوم
آشنایی با آزمایش واحد (unit testing) در دات نت قسمت سوم
آشنایی با آزمایش واحد (unit testing) در دات نت قسمت چهارم
آشنایی با آزمایش واحد (unit testing) در دات نت قسمت پنجم
آشنایی با آزمایش واحد (unit testing) در دات نت قسمت ششم
آشنایی با mocking frameworks - قسمت اول
آشنایی با mocking frameworks - قسمت دوم
چگونه کد قابل تست بنویسیم - قسمت اول
چگونه کد قابل تست بنویسیم - قسمت دوم
دیگر مقالات
آشنایی با آزمایش واحد (unit testing) در دات نت قسمت دوم
آشنایی با آزمایش واحد (unit testing) در دات نت قسمت سوم
آشنایی با آزمایش واحد (unit testing) در دات نت قسمت چهارم
آشنایی با آزمایش واحد (unit testing) در دات نت قسمت پنجم
آشنایی با آزمایش واحد (unit testing) در دات نت قسمت ششم
آشنایی با mocking frameworks - قسمت اول
آشنایی با mocking frameworks - قسمت دوم
چگونه کد قابل تست بنویسیم - قسمت اول
چگونه کد قابل تست بنویسیم - قسمت دوم
Test Driven Development
Microsoft Test Manager
نوشتن آزمونهای واحد به کمک کتابخانهی Moq
- تولید خودکار آزمونهای واحد NUnit
- تهیه آزمون واحد جهت کار با محتوای فایلها
- آزمون واحد Entity Framework به کمک چارچوب تقلید
- استفاده از shim و stub برای mock کردن در آزمون واحد
- آزمون واحد در MVVM به کمک تزریق وابستگی
- بررسی میزان پوشش آزمونهای واحد به کمک برنامه PartCover
- قالبی برای ایجاد آزمونهای NUnit مخصوص ReSharper
- آشنایی با تست واحد و استفاده از کتابخانه Moq
- WatIn - Web Application Testing in .Net
- آموزش Coded UI Test #1
- آشنایی با Should Library
- آماده سازی زیرساخت تهیه Integration Tests برای ServiceLayer
برنامه نویسهای سیشارپ پیشتر با null-coalescing operator یا ?? آشنا شده بودند. برای مثال
در این حالت اگر data یا سمت چپ عملگر، نال باشد، مقدار value (سمت راست عملگر) بازگشت داده خواهد شد؛ که در حقیقت خلاصه شدهی چند سطر ذیل است:
در سی شارپ 6، جهت تکمیل عملگرهای کار با مقادیر نال و بالا بردن productivity برنامه نویسها، عملگر دیگری به نام Null-conditional operator و یا .? به این مجموعه اضافه شدهاست. در این حالت ابتدا مقدار سمت چپ عملگر بررسی خواهد شد. اگر مقدار آن مساوی نال بود، در همینجا کار خاتمه یافته و نال بازگشت داده میشود. در غیر اینصورت کار بررسی زنجیرهی جاری ادامه خواهد یافت.
برای مثال بسیاری از نتایج بازگشتی از متدها، چند سطحی هستند:
در اینجا روش مرسوم کار با کلاس درخواست اطلاعات از وب به صورت ذیل است:
چون میخواهیم به خاصیت Result دسترسی پیدا کنیم، نیاز است دو مرحله وضعیت خروجی متد و همچنین خاصیت Result آنرا جهت مشخص سازی نال نبودن آنها، بررسی کنیم و اگر برای مثال خاصیت Result نیز خود متشکل از یک کلاس دیگر بود که در آن برای مثال StatusCode نیز ذکر شده بود، این بررسی به سه سطح یا بیشتر نیز ادامه پیدا میکرد.
در این حالت اگر اشارهگر را به محل && انتقال دهیم، افزونهی ReSharper پیشنهاد یکی کردن این بررسیها را ارائه میدهد:
به این ترتیب تمام چند سطح بررسی نال، به یک عبارت بررسی .? دار، خلاصه خواهد شد:
در اینجا ابتدا بررسی میشود که آیا webData نال است یا خیر؟ اگر نال بود همینجا کار خاتمه پیدا میکند و به بررسی Result نمیرسد. اگر نال نبود، ادامهی زنجیره تا به انتها بررسی میشود.
البته باید دقت داشت که برای تمام سطوح باید از .? استفاده کرد (برای مثال response?.Results?.Status)؛ در غیر اینصورت همانند سابق در صورت استفادهی از دات معمولی، به یک null reference exception میرسیم.
کار با متدها و Delegates
این عملگر جدید مقایسهی با نال را بر روی متدها (علاوه بر خواص و فیلدها) نیز میتوان بکار برد. برای مثال خلاصه شدهی فراخوانی ذیل:
با استفاده از Null Conditional Operator به این صورت است:
و یا بکار گیری آن بر روی delegates (روش قدیمی):
نیز با استفاده از متد Invoke به نحو ذیل قابل انجام است و نکته جالب یک سطر کد ذیل علاوه بر ساده شدن آن:
Thread-safe بودن آن نیز میباشد. زیرا در این حالت کامپایلر delegate را به یک متغیر موقتی کپی کرده و سپس فراخوانیها را انجام میدهد. اگر انجام این کپی موقت صورت نمیگرفت، در حین فراخوانی آن از طریق چندین ترد مختلف، ممکن بود یکی از مشترکین delegate از آن قطع اشتراک میکرد و در این حالت فراخوانی تردی دیگر در همان لحظه، سبب کرش برنامه میشد.
استفاده از Null Conditional Operator بر روی Value types
الف) مقایسه با نال
کد ذیل را درنظر بگیرید:
در اینجا Code یک value type از نوع int است. در این حالت با بکارگیری Null Conditional Operator، خروجی این حاصل، از نوع <Nullable<int و یا ?int درنظر گرفته خواهد شد و با توجه به اینکه عبارات null>0 و همچنین null<0 هر دو false هستند، مقایسهی این خروجی با 0 بدون مشکل انجام میشود. برای مثال مقایسهی ذیل از نظر کامپایلر یک عبارت معتبر است و بدون مشکل کامپایل میشود:
ب) بازگشت مقدار پیش فرض دیگری بجای نال
اگر نیاز بود بجای null مقدار پیش فرض دیگری را بازگشت دهیم، میتوان از null-coalescing operator سابق استفاده کرد:
در این مثال خاصیت CountT در اصل از نوع int تعریف شدهاست؛ اما بکارگیری .? سبب Nullable شدن آن خواهد شد. بنابراین امکان بکارگیری عملگر ?? یا null-coalescing operator نیز بر روی این متغیر وجود دارد.
ج) دسترسی به مقدار Value یک متغیر nullable
نمونهی دیگر آن قطعه کد ذیل است:
در اینجا برخلاف متغیر Code که از ابتدا nullable تعریف نشدهاست، متغیر x نال پذیر است. اما باید دقت داشت که با تعریف .? دیگر نیازی به استفاده از خاصیت Value این متغیر nullable نیست؛ زیرا .? سبب محاسبه و بازگشت خروجی آن میشود. بنابراین در این حالت، سطر دوم غیرمعتبر است (کامپایل نمیشود) و سطر سوم معتبر.
کار با indexer property و بررسی نال
اگر به عنوان بحث دقت کرده باشید، یک s جمع در انتهای Null-conditional operators ذکر شدهاست. به این معنا که این عملگر مقایسهی با نال، صرفا یک شکل و فرم .? را ندارد. مثال ذیل در حین کار با آرایهها و لیستها بسیار مشاهده میشود:
در اینجا به علت بکارگیری indexer بر روی Addresses، دیگر نمیتوان از عملگر .? که صرفا برای فیلدها، خواص، متدها و delegates طراحی شدهاست، استفاده کرد. به همین منظور، عملگر بررسی نال دیگری به شکل […]? برای این بررسی طراحی شدهاست:
به این ترتیب 5 سطح بررسی نال فوق، به یک عبارت کوتاه کاهش مییابد.
موارد استفادهی ناصحیح از عملگرهای مقایسهی با نال
خوب، عملگر .? کار مقایسهی با نال را خصوصا در دسترسیهای چند سطحی به خواص و متدها بسیار ساده میکند. اما آیا باید در همه جا از آن استفاده کرد؟ آیا باید از این پس کلا استفاده از دات را فراموش کرد و بجای آن از .? در همه جا استفاده کرد؟
مثال ذیل را درنظر بگیرید:
در این مثال در تمام سطوح آن از .? بجای دات استفاده شدهاست و بدون مشکل کامپایل میشود. اما این نوع فراخوانی سبب خواهد شد تا یک سری از مشکلات موجود کاملا مخفی شوند؛ خصوصا اعتبارسنجیها. برای مثال در این فراخوانی اگر مشتری نال باشد یا اگر کارمندانی را نداشته باشد، آدرسی بازگشت داده نمیشود. بنابراین حداقل دو سطح بررسی و اعتبارسنجی عدم وجود مشتری یا عدم وجود کارمندان آن در اینجا مخفی شدهاند و دیگر مشخص نیست که علت بازگشت نال چه بودهاست.
روش بهتر انجام اینکار، بررسی وضعیت customer و انتقال مابقی زنجیرهی LINQ به یک متد مجزای دیگر است:
string data = null; var result = data ?? "value";
if (data == null) { data = "value"; } var result = data;
برای مثال بسیاری از نتایج بازگشتی از متدها، چند سطحی هستند:
class Response { public string Result { set; get; } public int Code { set; get; } } class WebRequest { public Response GetDataFromWeb(string url) { // ... return new Response { Result = null }; } }
var webData = new WebRequest().GetDataFromWeb("https://www.dntips.ir/"); if (webData != null && webData.Result != null) { Console.WriteLine(webData.Result); }
در این حالت اگر اشارهگر را به محل && انتقال دهیم، افزونهی ReSharper پیشنهاد یکی کردن این بررسیها را ارائه میدهد:
به این ترتیب تمام چند سطح بررسی نال، به یک عبارت بررسی .? دار، خلاصه خواهد شد:
if (webData?.Result != null) { Console.WriteLine(webData.Result); }
البته باید دقت داشت که برای تمام سطوح باید از .? استفاده کرد (برای مثال response?.Results?.Status)؛ در غیر اینصورت همانند سابق در صورت استفادهی از دات معمولی، به یک null reference exception میرسیم.
کار با متدها و Delegates
این عملگر جدید مقایسهی با نال را بر روی متدها (علاوه بر خواص و فیلدها) نیز میتوان بکار برد. برای مثال خلاصه شدهی فراخوانی ذیل:
if (x != null) { x.Dispose(); }
x?.Dispose();
و یا بکار گیری آن بر روی delegates (روش قدیمی):
var copy = OnMyEvent; if (copy != null) { copy(this, new EventArgs()); }
OnMyEvent?.Invoke(this, new EventArgs());
استفاده از Null Conditional Operator بر روی Value types
الف) مقایسه با نال
کد ذیل را درنظر بگیرید:
var code = webData?.Code;
if (webData?.Code > 0) { }
ب) بازگشت مقدار پیش فرض دیگری بجای نال
اگر نیاز بود بجای null مقدار پیش فرض دیگری را بازگشت دهیم، میتوان از null-coalescing operator سابق استفاده کرد:
int count = response?.Results?.Count ?? 0;
ج) دسترسی به مقدار Value یک متغیر nullable
نمونهی دیگر آن قطعه کد ذیل است:
int? x = 10; //var value = x?.Value; // invalid Console.WriteLine(x?.ToString());
کار با indexer property و بررسی نال
اگر به عنوان بحث دقت کرده باشید، یک s جمع در انتهای Null-conditional operators ذکر شدهاست. به این معنا که این عملگر مقایسهی با نال، صرفا یک شکل و فرم .? را ندارد. مثال ذیل در حین کار با آرایهها و لیستها بسیار مشاهده میشود:
if (response != null && response.Results != null && response.Results.Addresses != null && response.Results.Addresses[0] != null && response.Results.Addresses[0].Zip == "63368") { }
if(response?.Results?.Addresses?[0]?.Zip == "63368") { }
موارد استفادهی ناصحیح از عملگرهای مقایسهی با نال
خوب، عملگر .? کار مقایسهی با نال را خصوصا در دسترسیهای چند سطحی به خواص و متدها بسیار ساده میکند. اما آیا باید در همه جا از آن استفاده کرد؟ آیا باید از این پس کلا استفاده از دات را فراموش کرد و بجای آن از .? در همه جا استفاده کرد؟
مثال ذیل را درنظر بگیرید:
public void DoSomething(Customer customer) { string address = customer?.Employees ?.SingleOrDefault(x => x.IsAdmin)?.Address?.ToString(); SendPackage(address); }
روش بهتر انجام اینکار، بررسی وضعیت customer و انتقال مابقی زنجیرهی LINQ به یک متد مجزای دیگر است:
public void DoSomething(Customer customer) { Contract.Requires(customer != null); string address = customer.GetAdminAddress(); SendPackage(address); }
نظرات مطالب
ASP.NET MVC #12
- ویژگیها یا Attributes در دات نت، استاتیک متادیتا هستند؛ مانند تعداد پارامترها، نام متدها و امثال آن که به صورت کامپایل شده در فایل باینری نهایی قرار میگیرند و نهایتا از طریق Reflection قابل دسترسی خواهند بود. تغییر آنها یا افزودن آنها عموما از طریق دستکاری کدهای IL میسر است یا از روشهای IL Code weaving مباحث AOP یا روشهایی مانند Reflection.Emit و همانند آن.
- یک استثناء در اینجا وجود دارد و آن هم متد TypeDescriptor.AddAttributes است که در زمان اجرا کار میکند. استفاده از آن هم فقط زمانی جواب خواهد داد که فریم ورک پایه از متد TypeDescriptor.GetAttributes برای یافتن ویژگیها استفاده کرده باشد.
- یک استثناء در اینجا وجود دارد و آن هم متد TypeDescriptor.AddAttributes است که در زمان اجرا کار میکند. استفاده از آن هم فقط زمانی جواب خواهد داد که فریم ورک پایه از متد TypeDescriptor.GetAttributes برای یافتن ویژگیها استفاده کرده باشد.
نظرات مطالب
EF Code First #1
1 و 3 - در انتهای بحث عرض کردم در قسمتهای بعدی خیلی از موارد رو توضیح خواهم داد. این قسمت اول و فقط یک «مقدمه» ابتدایی بود.
2 - EF با بانکهای اطلاعاتی NoSQL کار نمیکند. ضمنا هستند بانکهای اطلاعاتی NoSQL ایی که برای دات نت نوشته شدهاند و از همان روز اول با کلاسها و LINQ کار میکنید مانند RavenDB . طراحی فوق العادهای داره (^).
استفاده از EF Code first با سایر بانکهای اطلاعاتی بجز مشتقات SQL Server نیز میسر است. برای آنها نیاز به پروایدر مخصوص وجود دارد؛ مثلا: (^)
2 - EF با بانکهای اطلاعاتی NoSQL کار نمیکند. ضمنا هستند بانکهای اطلاعاتی NoSQL ایی که برای دات نت نوشته شدهاند و از همان روز اول با کلاسها و LINQ کار میکنید مانند RavenDB . طراحی فوق العادهای داره (^).
استفاده از EF Code first با سایر بانکهای اطلاعاتی بجز مشتقات SQL Server نیز میسر است. برای آنها نیاز به پروایدر مخصوص وجود دارد؛ مثلا: (^)