C# 7.0 – Pattern Matching
C# 7.0 – Pattern Matching
Pattern Matching in C# 7.0 Case Blocks
C# 7.0 Pattern Matching. Part1
Pattern Matching In C# 7.0
C#7: Pattern Matching
پیش از ارائه نهایی مطلب، تمام کدهای آن با VS 2017 RTM آزمایش و بررسی شوند.
- سبک وزن است
- بسیار سریع است
- به صورت سورس باز توسعه پیدا میکند
- رایگان است
- چندسکویی است
- انواع و اقسام زبانهای برنامه نویسی را پشتیبانی میکند
- پشتیبانی بسیار مناسبی را از طرف جامعهی برنامه نویسان به همراه دارد
- به همراه تعداد زیادی افزونه است
هدف اصلی از توسعهی آن نیز ارائهی تجربهی کاربری یکسانی در سکوهای کاری مختلف و زبانهای متفاوت برنامه نویسی است. در اینجا مهم نیست که از ویندوز، مک یا لینوکس استفاده میکنید. نحوهی کار کردن با آن در این سکوهای کاری تفاوتی نداشته و یکسان است. همچنین برای آن تفاوتی نمیکند که با PHP کار میکنید یا ASP.NET. تمام گروههای مختلف برنامه نویسان دسترسی به یک IDE بسیار سریع و سبک وزن را خواهند داشت.
برخلاف نگارش کامل ویژوال استودیو که این روزها حجم دریافت آن به بالای 20 گیگابایت رسیدهاست، VS Code با هدف سبک وزن بودن و سادگی دریافت و نصب، طراحی و توسعه پیدا میکند. این مورد، مزیت دریافت به روز رسانیهای منظم را بدون نگرانی از دریافت حجمهای بالا، برای بسیاری از علاقمندان مسیر میکند.
همچنین برای کار با نگارشهای جدیدتر ASP.NET Core، دیگر نیازی به دریافت آخرین به روز رسانیهای چندگیگابایتی ویژوال استودیوی کامل نبوده و میتوان کاملا مستقل از آن، از آخرین نگارش NET Core. و ASP.NET Core به سادگی در VSCode استفاده کرد.
نصب VS Code بر روی ویندوز
آخرین نگارش این محصول را از آدرس https://code.visualstudio.com میتوانید دریافت کنید. نصب آن نیز بسیار سادهاست؛ فقط گزینهی Add to PATH را نیز در حین نصب حتما انتخاب نمائید (هرچند به صورت پیش فرض نیز انتخاب شدهاست). به این ترتیب امکان استفادهی از آن در کنسولهای متفاوتی مسیر خواهد شد.
در ادامه فرض کنید که مسیر D:\vs-code-examples\sample01 حاوی اولین برنامهی ما خواهد بود. برای اینکه در اینجا بتوانیم، تجربهی کاربری یکسانی را مشاهده کنیم، از طریق خط فرمان به این پوشه وارد شده و دستور ذیل را صادر میکنیم:
D:\vs-code-examples\sample01>code .
نصب VS Code بر روی Mac
نصب VS Code بر روی مک یا لینوکس نیز به همین ترتیب است و زمانیکه به آدرس فوق مراجعه میکنید، به صورت خودکار نوع سیستم عامل را تشخیص داده و بستهی متناسبی را به شما پیشنهاد میکند. پس از دریافت بستهی آن برای مک، یک application را دریافت خواهید کرد که آنرا میتوان به مجموعهی Applications سیستم اضافه کرد. تنها تفاوت تجربهی نصب آن با ویندوز، انتخاب گزینهی Add to PATH آن است و به صورت پیش فرض نمیتوان آنرا از طریق ترمینال در هر مکانی اجرا کرد. برای این منظور، پس از اجرای اولیهی VS Code، دکمههای Ctrl/Command+Shift+P را در VS Code فشرده و سپس path را جستجو کنید (در دستور یاد شده، Ctrl برای ویندوز و لینوکس است و Command برای Mac):
در اینجا گزینهی install 'code' command path را انتخاب کنید تا بتوان VS Code را از طریق ترمینال نیز به سادگی اجرا کرد. به این ترتیب امکان اجرای دستور . code که بر روی ویندوز نیز ذکر شد، در اینجا نیز میسر خواهد بود.
نصب VS Code بر روی لینوکس
در اینجا نیز با مراجعهی به آدرس https://code.visualstudio.com، بستهی متناسب با لینوکس، جهت دریافت پیشنهاد خواهد شد؛ برای مثال بستههای deb. برای توزیعهایی مانند اوبونتو و یا rpm. برای ردهت. به علاوه اگر بر روی علامت ^ کنار بستههای دانلود کلیک کنید، یک بستهی tar.gz. نیز قابل دریافت خواهد بود. تجربهی نصب آن نیز همانند نمونهی ویندوز است و Add to PATH آن به صورت خودکار انجام خواهد شد.
بررسی ابتدایی محیط VS Code
VS Code بر اساس فایلهای قرار گرفتهی در یک پوشه و زیر پوشههای آن کار میکند. به همین جهت پس از صدور دستور . code، آن پوشه را در IDE خود نمایش خواهد داد. در اینجا برخلاف نگارش کامل ویژوال استودیو، روش کار، مبتنی بر یک فایل پروژه نیست و اگر خارج از VS Code نیز فایلی را به پوشهی باز شده اضافه کنید، بلافاصله تشخیص داده شده و در اینجا لیست میشود. هرچند یک چنین تجربهی کاربری با پروژههای ASP.NET Core نیز در نگارشهای جدیدتر ویژوال استودیوی کامل، سعی شدهاست شبیه سازی شود؛ برخلاف سایر پروژههای ویژوال استودیو که اگر فایلی در فایل پروژهی آن مدخلی نداشته باشد، به صورت پیش فرض نمایش داده نشده و درنظر گرفته نمیشود.
در ادامه برای نمونه از طریق منوی File->New File، یک فایل جدید را اضافه میکنیم. هرچند میتوان اشارهگر ماوس را بر روی نام پوشه نیز برده و از دکمههای نوار ابزار آن نیز برای ایجاد یک فایل و یا پوشهی جدید نیز استفاده کرد:
در اینجا فرمت ابتدایی فایل جدید را plain text تشخیص میدهد:
برای تغییر این حالت یا میتوان فایل را ذخیره کرد و پسوند مناسبی را برای آن انتخاب نمود و یا در همان status bar پایین صفحه، بر روی plain text کلیک کنید تا منوی انتخاب زبان ظاهر شود:
به این ترتیب پیش از ذخیرهی فایل با پسوندی مناسب نیز میتوان زبان مدنظر را تنظیم کرد. پس از آن، intellisense و syntax highlighting متناسب با آن زبان در دسترس خواهند بود.
بررسی تنظیمات VS Code
از طریق منوی File->Preferences->Settings میتوان به تنظیمات VS Code دسترسی یافت.
در اینجا در سمت چپ، لیست تنظیمات مهیا و پیش فرض این محیط قرار دارند و در سمت راست میتوان این پیش فرضها را (پس از بررسی و جستجوی آنها در پنل سمت چپ) بازنویسی و سفارشی سازی کرد.
تنظیمات انجام شدهی در اینجا را میتوان به پوشهی جاری نیز محدود کرد. برای این منظور بر روی لینک work space settings در کنار لینک user settings در تصویر فوق کلیک کنید. در این حالت یک فایل json را در پوشهی vscode. نمای جاری VSCode، ایجاد خواهد کرد (sample01\.vscode\settings.json) که میتواند در برگیرندهی تنظیمات سفارشی محدود و مختص به این پروژه و یا نما باشد.
یک نکته: تمام گزینههای منوی VS Code را و حتی مواردی را که در منوها لیست نشدهاند، میتوانید در Command Pallet آن با فشردن دکمههای Ctrl/Command+Shift+P نیز مشاهده کنید و به علاوه جستجوی آن نیز بسیار سریعتر است از دسترسی و کار مستقیم با منوها.
همچنین در اینجا اگر قصد یافتن سریع فایلی را داشته باشید، میتوانید دکمههای Ctrl/Command+P را فشرده و سپس نام فایل را جستجو کرد:
این دو دستور، جزو دستورات پایهای این IDE هستند و مدام از آنها استفاده میشود.
نصب افزونهی #C
اولین افزونهای را که جهت کار با ASP.NET Core نیاز خواهیم داشت، افزونهی #C است. برای این منظور در نوار ابزار عمودی سمت چپ صفحه، گزینهی Extensions را انتخاب کنید:
در اینجا افزونهی #C مایکروسافت را جستجو کرده و نصب کنید. نصب آن نیز بسیار ساده است. با حرکت اشارهگر ماوس بر روی آن، دکمهی install ظاهر میشود یا حتی اگر آنرا در لیست انتخاب کنیم، در سمت راست صفحه علاوه بر مشاهدهی جزئیات آن، دکمههای نصب و عزل نیز ظاهر خواهند شد.
تجربهی کاربری محیط نصب افزونههای آن نیز نسبت به نگارش کامل ویژوال استودیو، بسیار بهتر است. برای نمونه اگر به تصویر فوق دقت کنید، در همینجا میتوان جزئیات کامل افزونه، نویسنده یا نویسندگان آن و یا لیست تغییرات و وابستگیهای آنرا نیز بدون خروج از VSCode مشاهده و بررسی کرد. همچنین در دفعات بعدی اجرای VSCode، کار بررسی و نصب به روز رسانیهای این افزونهها نیز خودکار بوده و نیازی به بررسی دستی آنها نیست.
پس از نصب، دکمهی reload را ظاهر کرده و با کلیک بر روی آن، محیط جاری به صورت خودکار بارگذاری مجدد شده و بلافاصله قابل استفادهاست.
در قسمت بعد، اولین پروژهی ASP.NET Core خود را در VS Code ایجاد خواهیم کرد.
Options یا Maybe در یک زبان تابعی مثل #F، نشان دهندهی این است که شیء (Object) ممکن است وجود نداشته باشد(Null Reference) که یکی از مهمترین ویژگیهای یک زبان شیءگرا مثل #C و یا Java محسوب میشود. ما برنامه نویسها (اغلب) از هرچیزی که باعث کرش برنامه میشود، بیزاریم و برای اینکه برنامه کرش نکند، مجبور میشویم تمام کدهای خود را از Null Reference محافظت کنیم. تمام این مشکلات توسط Tony Hoare مخترع ALOGL است که تنها دلیل وجود Null References را سادگی پیاده سازی آن میداند و او این مورد را یک «خطای میلیون دلاری» نامیدهاست.
به این مثال توجه بفرمایید:
public class User { public int Id { get; set; } public string Name { get; set; } } public class UserService : IUserService { private IList<User> _userData; public UserService() { _userData = new List<User> { new User {Id = 1,Name = "ali"}, new User {Id = 2,Name = "Karim"} }; } public User GetById(int id) { return _userData.FirstOrDefault(x => x.Id == id); } } public class UserController : Controller { private readonly IUserService _userService; public UserController(IUserService userService) { _userService = userService; } public ActionResult Details(int id) { var user=_userService.GetById(3); // این متد ممکن است مقداری برگرداند و یا مقدار نال برگرداند if( user == null) return HttpNotFound(); return View(user); } }
این کدی است که ما برنامه نویسان به صورت متداولی با آن سروکار داریم. اما چه چیزی درباره این کد اشکال دارد؟
مشکل از آن جایی هست که ما نمیدانیم متد GetById مقداری را برمیگرداند و یا Null را بر میگرداند. این متد هرگاه که امکان برگرداندن Null وجود داشته باشد، خطای NullReferenceException را در زمان اجرا بر میگرداند و همان طور که میدانید، به ازای هر شرطی که به برنامه اضافه میکنیم، پیچیدگی برنامه هم افزایش مییابد و کد خوانایی خود را از دست میدهد. تصور کنید دنیایی بدون NullReferenceException چه دنیایی زیبایی میبود؛ ولی متاسفانه این مورد از ویژگیهای زبان #C است. خوشبختانه راهحلهای برای حل NRE ارائه شدهاند که در ادامه به آنها میپردازیم.
ما میخواهیم متد GetById همیشه چیزی غیر از نال را برگرداند و یکی از راههایی که ما را به این هدف میرساند این است که این متد یک توالی را برگرداند.
public class UserService : IUserService { private IList<User> _userData; public UserService() { _userData = new List<User> { new User {Id = 1,Name = "ali"}, new User {Id = 2,Name = "Karim"} }; } public IEnumerable<User> GetById(int id) { var user = _userData.FirstOrDefault(x => x.Id == id); if (user == null) return new User[0]; return new[] { user }; } }
اگر به امضای متد GetById توجه کنید، به جای اینکه User را برگرداند، این متد یک توالی از User را بر میگرداند و اگر در اینجا کاربری یافت شد، این توالی دارای یک المان خواهد بود و در غیر این صورت اگر User یافت نشد، این متد یک توالی را بر میگرداند که دارای هیچ المانی نیست. در ادامه اگر کلاینت بخواهد از متد GetById استفاده کند، به صورت زیر خواهد بود:
public ActionResult Details(int id) { var user = _userService .GetById(3) .DefaultIfEmpty(new User()) .Single(); return View(user); }
متد GetById دارای دو وجه است و وجه مثبت آن این است که اگر مجموعه دارای مقداری باشد، هیچ مشکلی نیست؛ ولی اگر مجموعه دارای المانی نباشد، باید یک شیء را به صورت پیش فرض به آن اختصاص دهیم که این کار را با استفاده از متد DefualtIfEmpty انجام دادهایم.
در اول مقاله هم اشاره کردیم که Maybe یا Options، مجموعهای است که دارای یک المان و یا هیچ المانی است. اگر به امضای متد GetById توجه کنید، متوجه خواهید شد که این متد میتواند مجموعهای را برگرداند و نمیتواند گارانتی کند که حتما مجموعهای را بر میگرداند که دارای یک المان و یا هیچ باشد. برای حل این مشکل میتوانیم از کلاس Option استفاده کنیم:
public class Option<T> : IEnumerable<T> { private readonly T[] _data; private Option(T[] data) { _data = data; } public static Option<T> Create(T element) => new Option<T>(new[] { element }); public static Option<T> CreateEmpty() => new Option<T>(new T[0]); public IEnumerator<T> GetEnumerator() => ((IEnumerable<T>) _data).GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); }
تنها دلیل استفاده از متدهای Create و CreateEmpty این است که به خوانایی برنامه کمک کنیم؛ نه بیشتر. در ادامه اگر بخواهیم از کلاس option استفاده کنیم، به صورت زیر خواهد بود:
public class UserService : IUserService { ... ... public Option<User> GetById(int id) { var user = _userData.FirstOrDefault(x => x.Id == id); return user == null ? Option<User>.CreateEmpty() : Option<User>.Create(user); } } public class UserController : Controller { ... ... public ActionResult Details(int id) { var user = _userService .GetById(3) .DefaultIfEmpty(new User()) .Single(); return View(user); } }
چکیده:
مدیریت کردن References کار بسیار پیچیدهای است. قبل از آن که تلاش کنیم مقداری را برگردانیم و یا عملیاتی را بر روی آن انجام دهیم، اول باید مطمئن شویم که این شیء به جایی اشاره میکند. نمونههای متفاوتی از Option و یا Maybe را میتوانید در اینترنت پیدا کنید که هدف نهایی آنها، حذف NullReferenceException است و آشنایی با این ایده، شما را به دنیای برنامه نویسی تابعی در#C هدایت میکند.
مثال 1: نوع <IEnumerator<T با حلقهی foreach سازگار نیست
public interface IEnumerator<out T> : IEnumerator, IDisposable { // // Summary: // Gets the element in the collection at the current position of the enumerator. // // Returns: // The element in the collection at the current position of the enumerator. T Current { get; } }
Error CS1579 foreach statement cannot operate on variables of type ‘IEnumerator’ because ‘IEnumerator’ does not contain a public instance or extension definition for ‘GetEnumerator’
static class Extensions { public static IEnumerator<T> GetEnumerator<T>(this IEnumerator<T> enumerator) => enumerator; }
اکنون حلقهی foreach را میتوان بر روی نوعهای <IEnumerator<T نیز بکار گرفت:
class Program { void Main() { var enumerator = Enumerable.Range(0, 10).GetEnumerator(); foreach (var item in enumerator) { Console.WriteLine(item); } } }
این نکته بر روی نمونهی async آن نیز قابل اعمال است که مثالی از آنرا در ادامه مشاهده میکنید:
static class Extensions { public static IAsyncEnumerator<T> GetAsyncEnumerator<T>(this IAsyncEnumerator<T> enumerator) => enumerator; } class Program { static async Task Main() { var enumerator = GetAsyncEnumerator(); await foreach (var item in enumerator) { Console.WriteLine(item); } } static async IAsyncEnumerator<int> GetAsyncEnumerator() { yield return 0; await Task.Delay(1); yield return 1; } }
مثال زیر را درنظر بگیرید:
class Program { static void Main() { foreach (var item in (1, 2, 3)) { Console.WriteLine(item); } } }
foreach statement cannot operate on variables of type '(int, int, int)' because '(int, int, int)' does not contain a public instance or extension definition for 'GetEnumerator' [CS9Features]csharp(CS1579)
static class Extensions { public static IEnumerator<object> GetEnumerator<T1, T2, T3>(this ValueTuple<T1, T2, T3> tuple) { yield return tuple.Item1; yield return tuple.Item2; } }
#C در بین زبانهای برتر سال 2014
معرفی AvalonStudio
ویژگیهای C# 11.0
امکان تعریف alias، قابلیت جدیدی نیست!
در نگارشهای پیشین زبان #C نیز میتوان برای نوعهای نامدار داتنت، alias/«نام مستعار» تعریف کرد؛ برای مثال:
using MyConsole = System.Console; MyConsole.WriteLine("Test console");
بنابراین دو حالت تعریف 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();
var rnd = new System.Random();
using Random = System.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 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}"); }
(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 استفاده شود.