مسیرراه‌ها
NHibernate
      نظرات مطالب
      EF Code First #1
      سلام مهندس نصیری، چرا این کد توی EF5 خطای کلید خارجی میده؟
      کدش از کتاب Code First که معرفی کردین استفاده کردم اما کد خودتون خطا نداره

      using System;
      using System.Collections.Generic;
      namespace ChapterOneProject
      {
      public class Patient
          {
              public Patient()
              {
                  Visits = new List<Visit>();
              }
      
              public int Id { get; set; }
              public string Name { get; set; }
              public DateTime BirthDate { get; set; }
              //[ForeignKey("AnimalTypeId")]
              
              public AnimalType AnimalType { get; set; }
              //public int AnimalTypeId { get; set; }
      
              public DateTime FirstVisit { get; set; }
              public List<Visit> Visits { get; set; }
          }
      
      public class Visit
          {
              [Key]
              public int Id { get; set; }
              public DateTime Date { get; set; }
              public String ReasonForVisit { get; set; }
              public String Outcome { get; set; }
              public Decimal Weight { get; set; }
      
              //[ForeignKey("PatientId")]
              //public virtual Patient Patient { get; set; }
              public int PatientId { get; set; }
          }
      
      public class AnimalType
          {
              public int Id { get; set; }
              public string TypeName { get; set; }
          }
      }

      کد کانتکست
      public class VetContext : DbContext
          {
              public DbSet<Patient> Patients { get; set; }
              public DbSet<Visit> Visits { get; set; }
              //public DbSet<AnimalType> AnimalTypes { get; set; }
          }
      و در تابع Main برنامه Console این نوشته شده اما خطا میده و ثبت نمی‌شه
      var dog = new AnimalType { TypeName = "Dog" };
                  var visit = new List<Visit>
                                  {
                                      new Visit
                                          {
                                              Date = new DateTime(2011, 9, 1),
                                              Outcome = "Test",
                                              ReasonForVisit = "Test",
                                              Weight = 32,
                                          }
                                  };
                  var patient = new Patient
                                    {
                                        Name = "Sampson",
                                        BirthDate = new DateTime(2008, 1, 28),
                                        AnimalType = dog,
                                        Visits = visit,
                                    };
                  using (var context = new VetContext())
                  {
                      context.Patients.Add(patient);
                      context.SaveChanges();
                  }

      کد‌های دیگه تست کردم مشکلی نداشت اما این مورد ؟
      با profiler چک کردم خطای عدم توانایی در تبدیل نوع datetime2 به datetime میده
      مطالب
      ساخت فرم های generic در WPF و Windows Application
      آیا می‌توان در یک پروژه های  Windows App یا WPF، یک فرم پایه به صورت generic تعریف کنیم و سایر فرم‌ها بتوانند از آن ارث ببرند؟ در این پست به تشریح و بررسی این مسئله  خواهیم پرداخت.
      در پروژه هایی به صورت Smart UI کد نویسی شده اند و یا حتی قصد انجام پروژه با تکنولوژی‌های WPF یا Windows Application را دارید و نیاز دارید که فرم‌های خود را به صورت generic بسازید این مقاله به شما کمک خواهد کرد.
      #Windows Application
      یک پروژه از نوع Windows Application ایجاد می‌کنیم و یک فرم به نام FrmBase در آن خواهیم داشت. یک Label در فرم قرار دهید  و مقدار Text آن را فرم اصلی قرار دهید.
      در فرم مربوطه، فرم را به صورت generic تعریف کنید. به صورت زیر:
      public partial class FrmBase<T> : Form where T : class 
          {
              public FrmBase()
              {
                  InitializeComponent();
              }
          }

      بعد باید همین تغییرات را در فایل FrmBase.designer.cs هم اعمال کنیم:
      partial class FrmBase<T> where T : class 
          {
              /// <summary>
              /// Required designer variable.
              /// </summary>
              private System.ComponentModel.IContainer components = null;
      
              /// <summary>
              /// Clean up any resources being used.
              /// </summary>
              /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
              protected override void Dispose( bool disposing )
              {
                  if ( disposing && ( components != null ) )
                  {
                      components.Dispose();
                  }
                  base.Dispose( disposing );
              }
      
              #region Windows Form Designer generated code
      
              /// <summary>
              /// Required method for Designer support - do not modify
              /// the contents of this method with the code editor.
              /// </summary>
              private void InitializeComponent()
              {
                  this.label1 = new System.Windows.Forms.Label();
                  this.SuspendLayout();
                  // 
                  // label1
                  // 
                  this.label1.AutoSize = true;
                  this.label1.Location = new System.Drawing.Point(186, 22);
                  this.label1.Name = "label1";
                  this.label1.Size = new System.Drawing.Size(51, 13);
                  this.label1.TabIndex = 0;
                  this.label1.Text = "فرم اصلی";
                  // 
                  // FrmBase
                  // 
                  this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
                  this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
                  this.ClientSize = new System.Drawing.Size(445, 262);
                  this.Controls.Add(this.label1);
                  this.Name = "FrmBase";
                  this.Text = "Form1";
                  this.ResumeLayout(false);
                  this.PerformLayout();
      
              }
      
              #endregion
      
              private System.Windows.Forms.Label label1;
          }
      یک فرم جدید بسازید و نام آن را FrmTest بگذارید. این فرم باید از FrmBase ارث ببرد. خب این کار را به صورت زیر انجام می‌دهیم:
      public partial class FrmTest : FrmBase<String>
          {
              public FrmTest()
              {
                  InitializeComponent();
              }
          }
      پروژه را اجرا کنید. بدون هیچ گونه مشکلی برنامه اجرا می‌شود وفرم مربوطه را در حالت اجرا مشاهده خواهید کرد. اما اگر قصد باز کردن فرم FrmTest را در حالت design داشته باشید با خطای زیر مواجه خواهید شد:

      با این که برنامه به راحتی اجرا می‌شود و خروجی آن قابل مشاهده است ولی امکان نمایش فرم در حالت design وجود ندارد. متاسفانه در Windows App‌ها برای تعریف فرم‌ها به صورت generic یا این مشکل روبرور هستیم. تنها راه موجود برای حل این مشکل استفاده از یک کلاس کمکی است. به صورت زیر:

          public partial class FrmTest : FrmTestHelp
          {
              public FrmTest()
              {
                  InitializeComponent();
              }
          }
      
          public class FrmTestHelp : FrmBase<String>
          {
          }
      مشاهده می‌کنید که بعد از اعمال تغییرات بالا فرم FrmTest به راحتی Load می‌شود و در حالت designer هم می‌تونید از آن استفاده کنید.
      #WPF
      در پروژه‌های WPF ، راه حلی برای این مشکل در نظر گرفته شده است. در WPF، برای Window یا UserControl پایه نمی‌توان Designer داشت. ابتدا باید فرم پایه را به صورت زیر ایجاد کنیم:
      public class WindowBase<T> : Window where T : class
      {
      }
      در این مرحله یک Window بسازید که از WindowBase ارث ببرد:
      public partial class MainWindow: WindowBase<String>
          {
              public MainWindow()
              {
                  InitializeComponent();
              }
          }
      در WPF باید تعاریف موجود برای Xaml و Code Behind یکی باشد. در نتیجه باید تغییرات زیر را در فایل Xaml نیز اعمال کنید:
      <local:WindowBase x:Class="GenericWindows.MainWindow" 
      x:TypeArguments="sys:String" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:GenericWindows" xmlns:sys="clr-namespace:System;assembly=mscorlib" Title="MainWindow" Height="350" Width="525"> <Grid> </Grid> </local:WindowBase>
      همان طور که می‌بیینید در ابتدای فایل به جای Window از local:WindowBase استفاده شده است. این نشان دهنده این است که فرم پایه برای این Window از نوع WindowBase است. برای مشخص کردن نوع generic هم می‌تونید از x:TypeArguments استفاده کنید که در این جا نوع آن را String انتخاب کردم.
      پاسخ به بازخورد‌های پروژه‌ها
      ارسال پارامترهای اختیاری به دستور Sql و مشکل با دستور is null
      - با درایور SQLite امتحان شد و مشکلی مشاهده نشد.
      - بنابراین این مورد بستگی دارد به توانایی‌های موتور و همچنین درایور بانک اطلاعاتی مورد استفاده (به نظر SQL-CE است) و یک مساله‌ی عمومی نیست.
      - برای حل این مشکل در SQL-CE از روش زیر استفاده کنید:
       ... WHERE f.Bar = @bar OR cast(@bar AS varchar(4000)) IS NULL
      مطالب دوره‌ها
      استفاده از async و await در برنامه‌های کنسول و سرویس‌های ویندوز NT
      فرض کنید می‌خواهید از await در متد Main یک برنامه‌ی کنسول به نحو ذیل استفاده کنید:
      using System;
      using System.Net;
      
      namespace Async15
      {
          class Program
          {
              static void Main(string[] args)
              {
                  using (var webClient = new WebClient { })
                  {
                      webClient.Headers.Add("User-Agent", "AsyncContext 1.0");
                      var data = await webClient.DownloadStringTaskAsync("https://www.dntips.ir");
                      Console.WriteLine(data);
                  }
              }
          }
      }
      کامپایلر چنین اجازه‌ای را نمی‌دهد. زیرا از await جایی می‌توان استفاده کرد که متد فراخوان آن با async مزین شده باشد و همچنین دارای یک Context باشد تا نتیجه را بتواند دریافت کند. اگر در اینجا سعی کنید async را به امضای متد Main اضافه نمائید، کامپایلر مجددا خطای an entry point cannot be marked with the 'async' modifier را صادر می‌کند.
      اضافه کردن واژه‌ی کلیدی async به روال‌های رخدادگردان void برنامه‌های دسکتاپ مجاز است؛ با توجه به اینکه متد async پیش از پایان کار به فراخوان بازگشت داده می‌شوند (ذات متدهای async به این نحو است). در برنامه‌های دسکتاپ، این بازگشت به UI event loop است؛ بنابراین برنامه بدون مشکل به کار خود ادامه خواهد داد. اما در اینجا، بازگشت متد Main، به معنای بازگشت به OS است و خاتمه‌ی برنامه. به همین جهت کامپایلر از async کردن آن ممانعت می‌کند.
      برای حل این مشکل در برنامه‌های کنسول و همچنین برنامه‌های سرویس ویندوز NT که دارای یک async-compatible context نیستند، می‌توان از یک کتابخانه‌ی کمکی سورس باز به نام Nito AsyncEx استفاده کرد. برای نصب آن دستور ذیل را در کنسول پاورشل نیوگت وارد کنید:
       PM> Install-Package Nito.AsyncEx
      پس از نصب برای استفاده از آن خواهیم داشت:
      using System;
      using System.Net;
      using Nito.AsyncEx;
      
      namespace Async15
      {
          class Program
          {
              static void Main(string[] args)
              {
                  AsyncContext.Run(async () =>
                  {
                      using (var webClient = new WebClient())
                      {
                          webClient.Headers.Add("User-Agent", "AsyncContext 1.0");
                          var data = await webClient.DownloadStringTaskAsync("https://www.dntips.ir");
                          Console.WriteLine(data);
                      }
                  });
              }
          }
      }
      Context ارائه شده در اینجا برخلاف مثال‌های قسمت‌های قبل، نیازی به فراخوانی متد همزمان Wait و یا خاصیت Result که هر دو از نوع blocking هستند ندارد و یک فراخوانی async واقعی است. همچنین می‌شد یک متد async void را نیز در اینجا برای استفاده از DownloadStringTaskAsync تعریف کرد (تا برنامه کامپایل شود). اما پیشتر عنوان شد که هدف از این نوع متدهای خاص async void صرفا استفاده از آن‌ها در روال‌های رخدادگردان UI هستند. زیرا ماهیت آن‌ها fire and forget است و برای دریافت نتیجه‌ی نهایی به نحوی باید ترد اصلی را قفل کرد. برای مثال در یک برنامه‌ی کنسول متد Console.ReadLine را در انتهای کار فراخوانی کرد. اما با استفاده از AsyncContext.Run نیازی به این کارها نیست.


      async lambda
      در مثال فوق از یک async lambda، برای فراخوانی استفاده شده است که به همراه دات نت 4.5 ارائه شده‌اند:
      Action, () => { }
      Func<Task>, async () => { await Task.Yield(); }
      
      Func<TResult>, () => { return 13; }
      Func<Task<TResult>>, async () => { await Task.Yield(); return 13; }
      آرگومان متد AsyncContext.Run از نوع Func of Task است. بنابراین برای مقدار دهی inline آن توسط lambda expressions مطابق مثال‌های فوق می‌توان از async lambda استفاده کرد.
      روش دوم استفاده از AsyncContext.Run و مقدار دهی Func of Task، تعریف یک متد مستقل async Task دار، به نحو ذیل است:
      class Program
      {
        static async Task<int> AsyncMain()
        {
          ..
        }
      
        static int Main(string[] args)
        {
          return AsyncContext.Run(AsyncMain);
        }
      }


      رخ‌دادهای مرتبط با طول عمر برنامه را async تعریف نکنید

      همانند متد Main که async تعریف کردن آن سبب بازگشت آنی روال کار به OS می‌شود و برنامه خاتمه می‌یابد، روال‌های رخدادگردانی که با طول عمر یک برنامه‌ی UI سر و کار دارند مانند Application_Launching، Application_Closing، Application_Activated و Application_Deactivated (خصوصا در برنامه‌های ویندوز 8) نیز نباید async void تعریف شوند (چون مطابق ذات متدهای async، بلافاصله به برنامه اعلام می‌کنند که کار تمام شد). در این موارد خاص نیز می‌توان از متد AsyncContext.Run برای انجام اعمال async استفاده کرد.
      مطالب
      امکان استفاده‌ی از قیود مسیریابی سفارشی ASP.NET Core در Blazor SSR برای رمزگشایی خودکار پارامترهای دریافتی

      در Blazor می‌توان مسیریابی‌های پارامتری را به صورت زیر نیز تعریف کرد:

      @page "/post/edit/{EditId:int}"
      

      که در اینجا EditId، یک پارامتر مسیریابی از نوع int تعریف شده و به صورت زیر در کدهای صفحه‌ی مرتبط، قابل دسترسی است:

      [Parameter] public int? EditId { set; get; }
      

      int تعریف شده‌ی در این مسیریابی، یک routing constraint و یا یک قید مسیریابی محسوب می‌شود و استفاده‌ی از آن، چنین مزایایی را به همراه دارد:

      - در این حالت فقط EditId های عددی پردازش می‌شوند و اگر رشته‌ای دریافت شود، کاربر با خروجی از نوع 404 و یا «یافت نشد»، مواجه خواهد شد.

      - امکان اعتبارسنجی مقادیر دریافتی، پیش از ارسال آن‌ها به صفحه و پردازش صفحه.

      قیود پیش‌فرض تعریف شده‌ی در Blazor

      اگر به مستندات مسیریابی Blazor مراجعه کنیم، به‌نظر فقط این موارد را می‌توان به‌عنوان قیود پارامترهای مسیریابی تعریف کرد:

      bool, datetime, decimal, double, float, guid, int, long, nonfile 
      

      و ... توضیحاتی در مورد اینکه آیا امکان بسط آن‌ها وجود دارند یا خیر، فعلا در مستندات رسمی آن، ذکر نشده‌اند.

      در Blazor 8x می‌توان از قیود مسیریابی سفارشی ASP.NET Core نیز استفاده کرد!

      ASP.NET Core سمت سرور، به همراه امکان سفارشی سازی قیودمسیریابی خود نیز هست که آن‌را می‌توان به کمک اینترفیسIRouteConstraint پیاده سازی کرد:

      namespace Microsoft.AspNetCore.Routing  
      {  
          public interface IRouteConstraint  
          {  
              bool Match(  
                  HttpContext httpContext,  
                  IRouter route,  
                  string routeKey,  
                  RouteValueDictionary values,  
                  RouteDirection routeDirection);  
          }  
      } 
      

      جالب اینجا است که می‌توان این نمونه‌های سفارشی را حداقل در نگارش جدید Blazor 8x SSR نیز استفاده کرد؛ هرچند در مستندات رسمی Blazor هنوز به آن‌ اشاره‌ای نشده‌است.

      در امضای متد Match فوق، دو پارامتر routeKey و values آن بیش از مابقی مهم هستند:

      - routeKey مشخص می‌کند که الان کدام پارامتر مسیریابی (مانند EditId در این مطلب) در حال پردازش است.

      - values، یک دیکشنری است که کلید هر عضو آن، پارامتر مسیریابی و مقدار آن، مقدار دریافتی از URL جاری است.

      - اگر این متد مقدار true را برگرداند، یعنی مسیریابی وارد شده‌ی به آن، با موفقیت پردازش و اعتبارسنجی شده و می‌توان صفحه‌ی مرتبط را نمایش داد؛ در غیراینصورت، کاربر پیام یافت نشدن آن صفحه و مسیر درخواستی را مشاهده می‌کند.

      پیاده سازی یک قید سفارشی رمزگشایی پارامترهای مسیریابی

      فرض کنید قصد ندارید که پارامترهای مسیریابی ویرایش رکوردهای خاصی را دقیقا بر اساس Id متناظر عددی آن‌ها در بانک اطلاعاتی، نمایش دهید؛ برای مثال نمی‌خواهید دقیقا آدرس post/edit/1 را به کاربر نمایش دهید؛ چون نمایش این اعداد عموما ساده و ترتیبی، حدس زدن آن‌ها را ساده‌ کرده و ممکن است در آینده مشکلات امنیتی را به همراه داشته باشد.

      می‌خواهیم از آدرس‌های متداول و ساده‌ی عددی زیر:

      @page "/post/edit/{EditId:int}"
      

      به آدرس رمزنگاری شده‌ی زیر برسیم:

      @page "/post/edit/{EditId:encrypt}"
      

      اگر به این آدرس جدید دقت کنید، در اینجا از نام قید جدیدی به نام encrypt استفاده شده‌است که جزو قیود پیش‌فرض سیستم مسیریابی Blazor نیست. روش تعریف آن به صورت زیر است:

      using System.Globalization;
      using DNTCommon.Web.Core;
      
      namespace Blazor8xSsrComponents.Utils;
      
      public class EncryptedRouteConstraint(IProtectionProviderService protectionProvider) : IRouteConstraint
      {
          public const string Name = "encrypt";
      
          public bool Match(HttpContext? httpContext,
              IRouter? route,
              string routeKey,
              RouteValueDictionary values,
              RouteDirection routeDirection)
          {
              ArgumentNullException.ThrowIfNull(routeKey);
              ArgumentNullException.ThrowIfNull(values);
      
              if (!values.TryGetValue(routeKey, out var routeValue))
              {
                  return false;
              }
      
              var valueString = Convert.ToString(routeValue, CultureInfo.InvariantCulture);
              values[routeKey] = string.IsNullOrEmpty(valueString) ? null : protectionProvider.Decrypt(valueString);
      
              return true;
          }
      }
      

      توضیحات:

      - در قیود سفارشی می‌توان سرویس‌ها را به سازنده‌ی کلاس تزریق کرد و برای مثال از سرویس IProtectionProviderService که در کتابخانه‌ی DNTCommon.Web.Core تعریف شده، برای رمزگشایی اطلاعات رسیده، استفاده کرده‌ایم.

      - یک نام را هم برای آن درنظر گرفته‌ایم که از این نام در فایل Program.cs به صورت زیر استفاده می‌شود:

      builder.Services.Configure<RouteOptions>(opt =>
      {
          opt.ConstraintMap.Add(EncryptedRouteConstraint.Name, typeof(EncryptedRouteConstraint));
      });
      

      یعنی زمانیکه سیستم مسیریابی به قید جدیدی به نام encrypt می‌رسد:

      @page "/post/edit/{EditId:encrypt}"
      

      آن‌را در لیست ConstraintMap ای که به نحو فوق به سیستم معرفی شده، جستجو می‌کند. اگر این نام یافت شد، سپس کلاس EncryptedRouteConstraint متناظر را نمونه سازی کرده و در جهت پردازش مسیر رسیده، مورد استفاده قرار می‌دهد.

      - در کلاس EncryptedRouteConstraint و متد Match آن، مقدار رشته‌ای EditId دریافت شده‌ی از طریق آدرس جاری درخواستی، رمزگشایی شده و بجای مقدار فعلی رمزنگاری شده‌ی آن درج می‌شود. همین اندازه برای مقدار دهی خودکار پارامتر EditId ذیل در صفحات مرتبط، کفایت می‌کند:

      [Parameter] public string? EditId { set; get; }
      

      یعنی دیگر نیازی نیست تا در صفحات مرتبط، کار رمزگشایی EditId، به صورت دستی انجام شود.

      نظرات مطالب
      C# 7 - Tuple return types and deconstruction
      مزیت خروجی‌های از نوع Tuple برای طراحان کتابخانه‌ها 
      تبدیل خروجی‌های dynamic قبلی
      // query dapper via "dynamic"
      int id = ...
      var row = conn.QuerySingle("select Name, LastUpdated from Records where Id=@id", new {id});
      string name = row.Name; // "dynamic"
      به tuple جدید
      // query dapper via C# 7 tuples
      int id = ...
      var row = conn.QuerySingle<(string name, DateTime lastUpdated)>(
           "select Name, LastUpdated from Records where Id=@id", new {id});
      // use row.name and row.lastUpdated
      با مزیت نداشتن هیچ نوع سربار اضافه‌ای (کد dynamic ابتدایی جهت پردازش، DLR را وارد صحنه می‌کند؛ اما کد فوق خیر. به علاوه چون value type است، حتی سربار heap allocation را هم ندارد).
      اشتراک‌ها
      کتابخانه CSharpFunctionalExtensions
      بازخوردهای پروژه‌ها
      نداشتن Relation بین موجودیت Comment و User
      سلام
      - چرا در تنظیمات Mapping موجودیت Comment (در CommentConfig ), تنظیمات مربوط به رابطه One-to-Many موجودیت Comment و AnonymousUser کامنت شده و چرا تنظیمات مربوط به رابطه One-to-Many موجودیت Comment و User انجام نشده است ؟
      - پس چجوری Relation بین جدول Comment و User و AnonymousUser برقرار میشه ؟
      - با توجه به اینکه یک رکورد Comment نباید فیلد UserId یا AnonymousUserId اون Null باشه(یعنی حتما یک یوزری ای کامنت رو ایجاد کرده و باید Id اون یوزر هم حتما ثبت بشه) , آیا Nullable بودن(HasOptional) فیلد کلید خارجی UserId و یا AnonymousUserId در موجودیت Comment درسته ؟ یا راه دیگه ای هست ؟
      متشکرم.
      مطالب
      C# 8.0 - Pattern Matching
      در نگارش‌های پیشین #C، بهبودهایی در زمینه‌ی Pattern matching وجود داشتند. در نگارش 8 نیز این بهبودها ادامه پیدا کرده‌اند که نتیجه‌ی آن به‌وجود آمدن روش جدیدی برای نوشتن عبارات switch است.


      معرفی روش جدید نوشتن عبارات switch در C#8.0

      فرض کنید یک enum که معرف تعدادی رنگ است را تعریف کرده‌ایم:
          public enum Rainbow
          {
              Red,
              Orange,
              Yellow,
              Green,
              Blue,
              Indigo,
              Violet
          }
      همچنین کلاسی را نیز جهت تشکیل اشیاء رنگ مبتنی بر RGB تدارک دیده‌ایم:
          class RGBColor
          {
              internal byte Red { get; }
              internal byte Green { get; }
              internal byte Blue { get; }
      
              internal RGBColor(byte red, byte green, byte blue)
              {
                  Red = red;
                  Green = green;
                  Blue = blue;
              }
      
              public override string ToString() => $"rgb({Red}, {Green}, {Blue})";
          }
      اکنون هدف ما این است که اگر یکی از اعضای این enum را انتخاب کردیم، بتوانیم معادل رنگ RGB آن‌را نیز داشته باشیم. برای این منظور می‌توان switch ساده‌ی زیر را تشکیل داد:
              internal static RGBColor FromRainbow(Rainbow rainbowBolor)
              {
                  switch (rainbowBolor)
                  {
                      case Rainbow.Red:
                          return new RGBColor(0xFF, 0x00, 0x00);
                      case Rainbow.Orange:
                          return new RGBColor(0xFF, 0x7F, 0x00);
                      case Rainbow.Yellow:
                          return new RGBColor(0xFF, 0xFF, 0x00);
                      case Rainbow.Green:
                          return new RGBColor(0x00, 0xFF, 0x00);
                      case Rainbow.Blue:
                          return new RGBColor(0x00, 0x00, 0xFF);
                      case Rainbow.Indigo:
                          return new RGBColor(0x4B, 0x00, 0x82);
                      case Rainbow.Violet:
                          return new RGBColor(0x94, 0x00, 0xD3);
                      default:
                          throw new ArgumentException(message: "invalid enum value", paramName: nameof(rainbowBolor));
                  };
              }
      این کاری است که تا پیش از C# 8.0 به صورت متداولی انجام می‌شود. اکنون در C# 8.0 می‌توان عبارت switch فوق را به صورت زیر خلاصه کرد:
              internal static RGBColor TasteTheRainbow(Rainbow rainbowColor) =>
                  rainbowColor switch
              {
                  Rainbow.Red => new RGBColor(0xFF, 0x00, 0x00),
                  Rainbow.Orange => new RGBColor(0xFF, 0x7F, 0x00),
                  Rainbow.Yellow => new RGBColor(0xFF, 0xFF, 0x00),
                  Rainbow.Green => new RGBColor(0x00, 0xFF, 0x00),
                  Rainbow.Blue => new RGBColor(0x00, 0x00, 0xFF),
                  Rainbow.Indigo => new RGBColor(0x4B, 0x00, 0x82),
                  Rainbow.Violet => new RGBColor(0x94, 0x00, 0xD3),
                  _ => throw new ArgumentException(message: "invalid enum value", paramName: nameof(rainbowColor)),
              };
      - در این روش جدید، بجای اینکه با ذکر switch و سپس، مقداری/نوعی شروع شود، ابتدا با نوع شروع می‌شود و سپس واژه‌ی کلیدی switch ذکر خواهد شد.
      - در ادامه تمام caseها حذف می‌شوند و بجای آن‌ها صرفا مقادیر مدنظر باقی می‌ماند. در اینجا <= به صورت expressed as خوانده می‌شود.
      - caseهای مختلف با کاما از هم جدا می‌شوند.
      - همچنین در سطر آخر آن نیز از یک discard استفاده شده‌است که معادل همان حالت default یا حالتی است که هیچ تطابقی صورت نگرفته باشد.
      - به علاوه اگر دقت کنید، نتیجه‌ی نهایی این switch جدید، به صورت یک مقدار، توسط متد TasteTheRainbow، بازگشت داده شده‌است. بنابراین نوشتن یک چنین عباراتی در C# 8.0، مجاز است:
      var operation = "+";
      int a = 1, b = 2;
      var result = operation switch
      {
         "+" => a + b,
         "-" => a - b,
         "/" => a / b,
           _ => throw new NotSupportedException()
      };


      معرفی Property Patterns در C# 8.0

      کلاس زیر را درنظر بگیرید که از تعدادی خاصیت عمومی تشکیل شده‌است:
          class Address
          {
              public string AddressLine1 { get; set; }
              public string AddressLine2 { get; set; }
              public string City { get; set; }
              public string State { get; set; }
              public string PostalCode { get; set; }
              public string CountryRegion { get; set; }
          }
      اکنون فرض کنید که می‌خواهیم مالیات فروش را بر اساس آدرس و محل آن، محاسبه کنیم. در C# 8.0 با معرفی قابلیت الگوهای خواص، می‌توان بر روی آدرس، یک switch را تشکیل داد و سپس تک تک خواص آن‌را ارزیابی کرد:
          static class PropertyPatterns
          {
              internal static decimal ComputeSalesTax(
                  Address location,
                  decimal salePrice) =>
                  location switch
              {
                  { State: "Fars" } => salePrice * 0.06m,
                  { State: "Tehran", City: "Tehran" } => salePrice * 0.056m,
      
                  // Other cases removed for brevity...
                  _ => 0M
              };
          }
      در اینجا، سمت چپ هر case، داخل یک {} قرار می‌گیرد و در آن می‌توان مقادیر چندین خاصیت شیء location دریافتی را بررسی کرد. برای نمونه در سطر دوم آن، روش ارزیابی بیش از یک خاصیت را نیز مشاهده می‌کنید که روش ذکر آن شبیه به تعریف شیء‌های JSON است. در آخر نیز توسط یک discard، حالت default ذکر شده‌است.


      معرفی Tuple Patterns در C# 8.0

      در switch‌های C# 8.0، می‌توان از tuples نیز برای تشکیل قسمت case و همچنین مقداری که قرار است switch بر روی آن صورت گیرد، استفاده کرد:
          static class TuplePatterns
          {
              internal static string RockPaperScissors(
                  string first,
                  string second)
                  => (first, second) switch
              {
                  ("rock", "paper") => "Rock is covered by Paper. Paper wins!",
                  ("rock", "scissors") => "Rock breaks Scissors. Rock wins!",
                  ("paper", "rock") => "Paper covers Rock. Paper wins!",
                  ("paper", "scissors") => "Paper is cut by Scissors. Scissors wins!",
                  ("scissors", "rock") => "Scissors is broken by Rock. Rock wins!",
                  ("scissors", "paper") => "Scissors cuts Paper. Scissors wins!",
                  (_, _) => "tie"
              };
          }
      در اینجا بر روی tuple ای که به صورت (first, second) تعریف شده، یک switch تعریف می‌شود. سپس برای نمونه 6 حالت مختلف برای آن پیش‌بینی شده و یک حالت default که آن نیز توسط discards معرفی می‌شود.


      بهبودهای Pattern Matching بر روی اشیاء در C# 8.0

      فرض کنید شیء پایه‌ی Shape را تعریف و بر اساس آن دو شیء جدید دایره و مستطیل را ایجاد کرده‌ایم:
          class Shape
          {
              protected internal double Height { get; }
              protected internal double Length { get; }
      
              protected Shape(double height = 0, double length = 0)
              {
                  Height = height;
                  Length = length;
              }
          }
      
          class Circle : Shape
          {
              internal double Radius => Height / 2;
              internal double Diameter => Radius * 2;
              internal double Circumference => 2 * Math.PI * Radius;
      
              internal Circle(double height = 10, double length = 10)
                  : base(height, length) { }
          }
      
          class Rectangle : Shape
          {
              internal bool IsSquare => Height == Length;
      
              internal Rectangle(double height = 10, double length = 10)
                  : base(height, length) { }
          }
      امکان Pattern Matching بر روی اشیاء، در C# 7x نیز وجود دارد؛ اما در C# 8.0 می‌توان از روش جدید بیان عبارت switch آن به صورت زیر نیز در این حالت استفاده کرد:
          static class ObjectPatterns
          {
              internal static string ShapeDetails(this Shape shape)
                  => shape switch
              {
                  Circle c => $"circle with (C): {c.Circumference}",
                  Rectangle s when s.IsSquare => $"L:{s.Length} H:{s.Height}, square",
                  Rectangle r => $"L:{r.Length} H:{r.Height}, rectangle",
                  _ => "Unknown shape!" // Discard
              };
          }
      در اینجا یک شیء، به متد ShapeDetails ارسال شده و سپس جزئیاتی از آن دریافت می‌شود. مطابق روش C# 8.0، در اینجا نیز کار با ذکر نوع و سپس عبارت switch، شروع می‌شود. در ادامه روش بررسی نوع‌ها را در caseهای این سوئیچ ملاحظه می‌کنید. اگر در قسمت case آن Circle c ذکر شد، یعنی نوع shape از نوع دایره بوده و همچنین در همینجا می‌توان متغیر c را بر این اساس تعریف کرد و از آن استفاده نمود و یا می‌توان به کمک واژه‌ی کلیدی when، بر روی این متغیری که جدید تعریف شده، شرطی را نیز بررسی کرد. حالت default آن هم توسط discards معرفی می‌شود.


      معرفی Positional Patterns در C# 8.0

      در اینجا یک Point را داریم که می‌خواهیم بر اساس آن یک Quadrant را استخراج کنیم:
          class Point
          {
              public int X { get; }
      
              public int Y { get; }
      
              public Point(int x, int y) => (X, Y) = (x, y);
      
              public void Deconstruct(out int x, out int y) => (x, y) = (X, Y);
          }
      
          enum Quadrant
          {
              Unknown,
              Origin,
              One,
              Two,
              Three,
              Four,
              OnBorder
          }
      برای این منظور می‌توان از الگوهای موقعیتی C# 8.0 استفاده کرد:
          static class PositionalPatterns
          {
              internal static Quadrant AsQuadrant(Point point) => point switch
              {
                  (0, 0) => Quadrant.Origin,
                  var (x, y) when x > 0 && y > 0 => Quadrant.One,
                  var (x, y) when x < 0 && y > 0 => Quadrant.Two,
                  var (x, y) when x < 0 && y < 0 => Quadrant.Three,
                  var (x, y) when x > 0 && y < 0 => Quadrant.Four,
                  (_, _) => Quadrant.OnBorder, // Either are 0, but not both
                  _ => Quadrant.Unknown
              };
          }
      اگر به کلاس Point دقت کنید، یک قسمت Deconstruct هم دارد. به همین جهت در قسمت‌های case این switch، زمانیکه برای مثال (0,0) ذکر می‌شود (که یک tuple literal است)، به صورت خودکار یک شیء Point متناظر را با مقادیر X و Y آن، تشکیل می‌دهد. همچنین روش‌های مختلف مقایسه‌ی مقادیر x و y این tuple را نیز در caseهای مختلف آن مشاهده می‌کنید.
      در اینجا اگر دقت کنید و case مخصوص discards معرفی شده‌است. اولی برای حالت‌هایی است که هیچکدام از شرایط پیش از آن را برآورده نمی‌کند، مانند حالت (1,0)، در غیراینصورت سطر بعد از آن بازگشت داده می‌شود.