مطالب
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)، در غیراینصورت سطر بعد از آن بازگشت داده می‌شود.
مطالب
مقدار دهی اولیه‌ی بانک اطلاعاتی توسط Entity framework Core
قابلیت مقدار دهی اولیه‌ی بانک اطلاعاتی (data seeding) توسط اجرای کدهای Migrations و متد DbMigration­Configuration.Seed آن، در حین انتقال از EF 6x به EF Core ناپدید شده بود که مجددا با ارائه‌ی EF Core 2.1 به نحو کاملا متفاوتی توسط یک Fluent API، در متد OnModelCreating قابل تعریف و استفاده‌است.


کلاس‌های موجودیت‌های مثال جاری

برای توضیح قابلیت جدید مقدار دهی اولیه‌ی بانک اطلاعاتی در +EF Core 2.1، از کلاس‌های موجودیت‌های ذیل استفاده خواهیم کرد:
public class Magazine
{
  public int MagazineId { get; set; }
  public string Name { get; set; }
  public string Publisher { get; set; }

  public List<Article> Articles { get; set; }
}

public class Article
{
  public int ArticleId { get; set; }
  public string Title { get; set; }
  public DateTime PublishDate { get;  set; }

  public int MagazineId { get; set; }

  public Author Author { get; set; }
  public int? AuthorId { get; set; }
}

public class Author
{
  public int AuthorId { get; set; }
  public string Name { get; set; }

  public List<Article> Articles { get; set; }
}


روش مقدار دهی اولیه‌ی تک موجودیت‌ها

اکنون فرض کنید قصد داریم جدول مجلات را مقدار دهی اولیه کنیم. برای اینکار خواهیم داشت:
protected override void OnModelCreating (ModelBuilder modelBuilder)
{
   modelBuilder.Entity<Magazine>().HasData(new Magazine { MagazineId = 1, Name = "DNT Magazine" });
}
چند نکته در اینجا حائز اهمیت هستند:
- ذکر صریح مقدار Id یک رکورد (هرچند نوع Id آن auto-increment است).
- عدم ذکر مقدار Publisher.

اکنون اگر توسط دستورات Migrations مانند dotnet ef migrations add init، کار تولید کدهای متناظر به روز رسانی بانک اطلاعاتی را بر اساس این کدها تولید کنیم، در قسمتی از آن، یک چنین خروجی را دریافت خواهیم کرد:
migrationBuilder.InsertData(
  table: "Magazines",
  columns: new[] { "MagazineId", "Name", "Publisher" },
  values: new object[] { 1, "DNT Magazine", null });
در ادامه اگر از روی این کلاس‌های مهاجرت‌ها، اسکریپت معادل نهایی اعمالی به بانک اطلاعاتی را توسط دستور dotnet ef migrations script تولید کنیم، یک چنین خروجی حاصل می‌شود:
set IDENTITY_INSERT ON
INSERT INTO "Magazines" ("MagazineId", "Name", "Publisher") VALUES (1, 'DNT Magazine', NULL);
همانطور که مشاهده می‌کنید، اگر نوع بانک اطلاعاتی ما SQL Server باشد، ابتدا ثبت دستی فیلدهای IDENTITY تنظیم می‌شود و سپس Id رکورد جدید را بر اساس مقداری که مشخص کرده‌ایم، درج می‌کند.

توسط متد HasData امکان درج چندین رکورد با هم نیز وجود دارد:
modelBuilder.Entity<Magazine>()
           .HasData(new Magazine{ MagazineId=2, Name="This Mag" },
                    new Magazine{ MagazineId=3, Name="That Mag" }
           );

البته باید دقت داشت که متد HasData، برای کار با یک تک موجودیت، طراحی شده‌است و توسط آن نمی‌توان در چندین جدول بانک اطلاعاتی، مقادیری را درج کرد.

در مورد داده‌های نال‌نپذیر چطور؟
در مثال فوق اگر تنظیمات خاصیت Publisherای را که نال وارد کردیم، نال‌نپذیر تعریف کنیم:
modelBuilder.Entity<Magazine>().Property(m=>m.Publisher).IsRequired();
و مجددا دستورات تولید کلاس‌های Migrations را صادر کنیم، اینبار خطای واضح زیر حاصل خواهد شد:
 "The seed entity for entity type 'Magazine' cannot be added because there was no value provided for the required property 'Publisher'."
همین پیام خطا با عدم ذکر صریح مقدار Id نیز تولید می‌شود. هرچند Id، یک فیلد auto-increment است، اما چون شرط IsRequired در مورد آن برقرار است، شامل بررسی فیلدهای نال‌نپذیر نیز می‌شود. به همین جهت ذکر آن در متد HasData اجباری است.


امکان استفاده‌ی از Anonymous Types در متد HasData

فرض کنید برای کلاس موجودیت خود یک سازنده را نیز تعریف کرده‌اید:
public Magazine(string name, string publisher)
{
  Name=name;
  Publisher=publisher;
}
چون در متد HasData ذکر Id موجودیت، اجباری است، دیگر نمی‌توان یک چنین تعاریفی را ارائه داد:
modelBuilder.Entity<Magazine>().HasData(new Magazine("DNT Magazine", "1105 Media"));
برای رفع یک چنین مشکلاتی، امکان استفاده‌ی از anonymous types نیز در متد HasData پیش‌بینی شده‌است. در این حالت می‌توان بجای وهله سازی مستقیم شیء Magazine، یک anonymous type را وهله سازی کرد و در آن MagazineId را نیز ذکر کرد؛ بدون اینکه نگران این باشیم آیا این خاصیت عمومی است، خصوصی است و یا ... حتی تعریف شده‌است یا خیر!
modelBuilder.Entity<Magazine>().HasData(new {MagazineId=1, Name="DNT Mag", Publisher="1105 Media"});
که حاصل آن تولید یک چنین کد مهاجرتی است:
migrationBuilder.InsertData(
                table: "Magazines",
                columns: new[] { "MagazineId", "Name", "Publisher" },
                values: new object[] { 1, "DNT Mag", "1105 Media" });
و سبب درج صحیح مقادیر فیلدهای یک رکورد جدول Magazines می‌شود.

حالت دیگر استفاده‌ی از این قابلیت، کار با خواصی هستند که private set می‌باشند. فرض کنید کلاس موجودیت Magazine را به صورت زیر تغییر داده‌اید:
public class Magazine
{
  public Magazine(string name, string publisher)
  {
    Name=name;
    Publisher=publisher;
    MagazineId=Guid.NewGuid();
  }

  public Guid MagazineId { get; private set; }
  public string Name { get; private set; }
  public string Publisher { get; private set; }
  public List<Article> Articles { get; set; }
}
که در آن Id اینبار از نوع Guid است و در سازنده‌ی کلاس مقدار دهی می‌شود و همچنین خواص این موجودیت به صورت private set تعریف شده‌اند. در این حالت اگر متد HasData این موجودیت را به صورت زیر تعریف کنیم:
modelBuilder.Entity<Magazine>().HasData(new Magazine("DNT Mag", "1105 Media");
هر بار که دستورات Migrations اجرا می‌شوند، یک Guid جدید به صورت خودکار ایجاد خواهد شد که سبب می‌شود، مقدار آغازین پیشین، از بانک اطلاعاتی حذف و مقدار جدید آن با یک Guid جدید، درج شود. به همین جهت نیاز است Guid را حتما به صورت دستی و مشخص، در متد HasData وارد کرد که چنین کاری با توجه به تعریف کلاس موجودیت فوق، مسیر نیست. بنابراین در اینجا نیز می‌توان از یک anonymous type استفاده کرد:
var mag1=new {MagazineId= new Guid("0483b59c-f7f8-4b21-b1df-5149fb57984e"),  Name="DNT Mag", Publisher="1105 Media"};
modelBuilder.Entity<Magazine>().HasData(mag1);


مقدار دهی اولیه‌ی اطلاعات به هم مرتبط

همانطور که پیشتر نیز ذکر شد، متد HasData تنها با یک تک موجودیت کار می‌کند و روش کار آن همانند کار با DbSetها نیست. به همین جهت نمی‌توان اشیاء به هم مرتبط را توسط آن در بانک اطلاعاتی درج کرد. بنابراین برای درج اطلاعات یک مجله و مقالات مرتبط با آن، ابتدا باید مجله را ثبت کرد و سپس بر اساس Id آن مجله، کلید خارجی مقالات را به صورت جداگانه‌ای مقدار دهی نمود:
modelBuilder.Entity<Article>().HasData(new Article { ArticleId = 1, MagazineId = 1, Title = "EF Core 2.1 Query Types"});
پیشتر یک Magazine را با Id مساوی 1 ثبت کرده بودیم. اکنون این Id را در اینجا به صورت یک کلید خارجی، جهت درج یک مقاله‌ی جدیدی استفاده می‌کنیم. حاصل آن یک چنین مهاجرتی است:
var mag1=new {MagazineId= new Guid("0483b59c-f7f8-4b21-b1df-5149fb57984e"),  Name="DNT Mag", Publisher="1105 Media"};
modelBuilder.Entity<Magazine>().HasData(mag1);
در اینجا چون PublishDate را ذکر نکرده‌ایم (و DateTime نیز یک value type است)، کمترین مقدار ممکن را برای آن تنظیم کرده‌است.


مقدار دهی اولیه‌ی Owned Entities

complex types در EF 6x با مفهوم دیگری به نام owned types در EF Core جایگزین شده‌اند:
public class Publisher
{
  public string Name { get; set; }
  public int YearFounded { get; set; }
}

public class Magazine
{ 
  public int MagazineId { get;  set; }
  public string Name { get;  set; }
  public Publisher Publisher { get;  set; }
  public List<Article> Articles { get; set; }
}
در اینجا اطلاعات مربوط به Publisher‌، در طی یک عملیات Refactoring، تبدیل به یک کلاس مستقل شده‌اند و سپس در تعریف کلاس موجودیت مجله، مورد استفاده قرار گرفته‌اند. این کلاس جدید، دارای Id نیست.
modelBuilder.Entity<Magazine>().HasData (new Magazine { MagazineId = 1, Name = "DNT Magazine" });
modelBuilder.Entity<Magazine>().OwnsOne (m => m.Publisher)
   .HasData (new { Name = "1105 Media", YearFounded = 2006, MagazineId=1 });
متد HasData تنها اجازه‌ی کار با یک نوع کلاس را می‌دهد. به همین جهت یکبار باید Magazine را بدون Publisher ثبت کرد. سپس در طی ثبتی دیگر می‌توان نوع Publisher را توسط یک anonymous type متصل به Id مجله‌ی ثبت شده، درج کرد (متد OwnsOne کار ارتباط را برقرار می‌کند). علت استفاده‌ی از anonymous type نیز درج Id ای است که در کلاس Publisher وجود خارجی ندارد.
این دو دستور، خروجی Migrations زیر را تولید می‌کنند:
migrationBuilder.InsertData(
  table: "Magazines",
  columns: new[] { "MagazineId", "Name", "Publisher_Name", "Publisher_YearFounded" },
  values: new object[] { 1, "DNT Magazine", "1105 Media", 2006 });


محل صحیح اجرای Migrations در برنامه‌های ASP.NET Core 2x

زمانیکه متد ()context.Database.Migrate را اجرا می‌کنید، تمام مهاجرت‌های اعمال نشده را به بانک اطلاعاتی اعمال می‌کند که این مورد شامل اجرای دستورات HasData نیز هست. روش فراخوانی این متد در ASP.NET Core 1x به صورت زیر در متد Configure کلاس Startup بود (و البته هنوز هم کار می‌کند):
namespace EFCoreMultipleDb.Web
{
    public class Startup
    {
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            applyPendingMigrations(app);
// ...
        }

        private static void applyPendingMigrations(IApplicationBuilder app)
        {
            var scopeFactory = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>();
            using (var scope = scopeFactory.CreateScope())
            {
                var uow = scope.ServiceProvider.GetService<IUnitOfWork>();
                uow.Migrate();
            }
        }
    }
}
متد applyPendingMigrations، کار وهله سازی IUnitOfWork را انجام می‌دهد. سپس متد Migrate آن‌را اجرا می‌کند، تا تمام Migartions تولید شده، اما اعمال نشده‌ی به بانک اطلاعاتی به صورت خودکار به آن اعمال شوند. متد Migrate نیز به صورت زیر تعریف می‌شود:
namespace EFCoreMultipleDb.DataLayer.SQLite.Context
{
    public class SQLiteDbContext : DbContext, IUnitOfWork
    {
    // ... 

        public void Migrate()
        {
            this.Database.Migrate();
        }
    }
}
روش بهتر اینکار در ASP.NET Core 2x، انتقال متد applyPendingMigrations به بالاترین سطح ممکن در برنامه، در فایل program.cs و پیش از اجرای متد Configure کلاس Startup است. به این ترتیب در برنامه، قسمت‌هایی که پیش از متد Configure شروع به کار می‌کنند و نیاز به دسترسی به بانک اطلاعاتی را دارند، با صدور پیام خطایی، سبب خاتمه‌ی برنامه نخواهند شد:
public static void Main(string[] args)
{
   var host = BuildWebHost(args);
   using (var scope = host.Services.CreateScope())
   {
       var context = scope.ServiceProvider.GetRequiredService<yourDBContext>();
       context.Database.Migrate();
   }
   host.Run();
}
مطالب
پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 6#
در ادامه پست پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 5# ، در این پست به تشریح کلاس دایره و بیضی می‌پردازیم.

ابتدا به تشریح کلاس ترسیم بیضی (Ellipse) می‌پردازیم.
using System.Drawing;

namespace PWS.ObjectOrientedPaint.Models
{
    /// <summary>
    /// Ellipse Draw
    /// </summary>
    public class Ellipse : Shape
    {
        #region Constructors (2)

        /// <summary>
        /// Initializes a new instance of the <see cref="Ellipse" /> class.
        /// </summary>
        /// <param name="startPoint">The start point.</param>
        /// <param name="endPoint">The end point.</param>
        /// <param name="zIndex">Index of the z.</param>
        /// <param name="foreColor">Color of the fore.</param>
        /// <param name="thickness">The thickness.</param>
        /// <param name="isFill">if set to <c>true</c> [is fill].</param>
        /// <param name="backgroundColor">Color of the background.</param>
        public Ellipse(PointF startPoint, PointF endPoint, int zIndex, Color foreColor, byte thickness, bool isFill, Color backgroundColor)
            : base(startPoint, endPoint, zIndex, foreColor, thickness, isFill, backgroundColor)
        {
            ShapeType = ShapeType.Ellipse;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Ellipse" /> class.
        /// </summary>
        public Ellipse()
        {
            ShapeType = ShapeType.Ellipse;
        }

        #endregion Constructors

        #region Methods (1)

        // Public Methods (1) 

        /// <summary>
        /// Draws the specified g.
        /// </summary>
        /// <param name="g">The g.</param>
        public override void Draw(Graphics g)
        {
            if (IsFill)
                g.FillEllipse(BackgroundBrush, StartPoint.X, StartPoint.Y, Width, Height);
            g.DrawEllipse(Pen, StartPoint.X, StartPoint.Y, Width, Height);
            base.Draw(g);
        }

        #endregion Methods
    }
}
این کلاس از شی Shape ارث برده و دارای دو سازنده ساده می‌باشد که نوع شی ترسیمی را مشخص می‌کنند، در متد Draw نیز با توجه به توپر یا توخالی بودن شی ترسیم آن انجام میشود، در این کلاس باید متد HasPointInShape بازنویسی (override) شود، در این متد باید تعیین شود که یک نقطه در داخل بیضی قرار گرفته است یا خیر که متاسفانه فرمول بیضی خاطرم نبود. البته به صورت پیش فرض نقطه با توجه به چهارگوشی که بیضی را احاطه می‌کند سنجیده می‌شود.

کلاس دایره (Circle) از کلاس بالا (Ellipse) ارث بری دارد که کد آن را در زیر مشاهده می‌نمایید.
using System;
using System.Drawing;

namespace PWS.ObjectOrientedPaint.Models
{
    /// <summary>
    /// Circle
    /// </summary>
    public class Circle : Ellipse
    {
#region Constructors (2) 

        /// <summary>
        /// Initializes a new instance of the <see cref="Circle" /> class.
        /// </summary>
        /// <param name="startPoint">The start point.</param>
        /// <param name="endPoint">The end point.</param>
        /// <param name="zIndex">Index of the z.</param>
        /// <param name="foreColor">Color of the fore.</param>
        /// <param name="thickness">The thickness.</param>
        /// <param name="isFill">if set to <c>true</c> [is fill].</param>
        /// <param name="backgroundColor">Color of the background.</param>
        public Circle(PointF startPoint, PointF endPoint, int zIndex, Color foreColor, byte thickness, bool isFill, Color backgroundColor)
        {
            float x = 0, y = 0;
            float width = Math.Abs(endPoint.X - startPoint.X);
            float height = Math.Abs(endPoint.Y - startPoint.Y);
            if (startPoint.X <= endPoint.X && startPoint.Y <= endPoint.Y)
            {
                x = startPoint.X;
                y = startPoint.Y;
            }
            else if (startPoint.X >= endPoint.X && startPoint.Y >= endPoint.Y)
            {
                x = endPoint.X;
                y = endPoint.Y;
            }
            else if (startPoint.X >= endPoint.X && startPoint.Y <= endPoint.Y)
            {
                x = endPoint.X;
                y = startPoint.Y;
            }
            else if (startPoint.X <= endPoint.X && startPoint.Y >= endPoint.Y)
            {
                x = startPoint.X;
                y = endPoint.Y;
            }
            StartPoint = new PointF(x, y);
            var side = Math.Max(width, height);
            EndPoint = new PointF(x + side, y + side);
            ShapeType = ShapeType.Circle;
            Zindex = zIndex;
            ForeColor = foreColor;
            Thickness = thickness;
            BackgroundColor = backgroundColor;
            IsFill = isFill;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Circle" /> class.
        /// </summary>
        public Circle()
        {
            ShapeType = ShapeType.Circle;
        }

#endregion Constructors 

#region Methods (1) 

// Public Methods (1) 

        /// <summary>
        /// Points the in sahpe.
        /// </summary>
        /// <param name="point">The point.</param>
        /// <param name="tolerance">The tolerance.</param>
        /// <returns>
        ///   <c>true</c> if [has point in sahpe] [the specified point]; otherwise, <c>false</c>.
        /// </returns>
        public override bool HasPointInSahpe(PointF point, byte tolerance = 5)
        {
            float width = Math.Abs(EndPoint.X+tolerance - StartPoint.X-tolerance);
            float height = Math.Abs(EndPoint.Y+tolerance - StartPoint.Y-tolerance);
            float diagonal = Math.Max(height, width);
            float raduis = diagonal / 2;
            float dx = Math.Abs(point.X - (X + Width / 2));
            float dy = Math.Abs(point.Y - (Y + height / 2));
            return (dx + dy <= raduis);
        }

#endregion Methods 
    }
}
این کلاس شامل دو سازنده می‌باشد، که در سازنده اول با توجه به نقاط ایتدا و انتهای ترسیم شکل مقدار طول و عرض مستطیل احاطه کننده دایره محاسبه شده و باتوجه به آنها بزرگترین ضلع به عنوان قطر دایره در نظر گرفته می‌شود و EndPoint شکل مورد نظر تعیین می‌شود.

در متد HasPointInShape  با استفاده از فرمول دایره تعیین می‌شود که آیا نقطه پارامتر ورودی متد در داخل دایره واقع شده است یا خیر (جهت انتخاب شکل برای جابجایی یا تغییر اندازه).
در پست‌های بعد به پیاده سازی اینترفیس نرم افزار خواهیم پرداخت.

موفق و موید باشید

در ادامه مطالب قبل:
پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 1# 
پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 2# 
پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 3#
پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 4# 
پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 5# 
مطالب
آموزش LightInject IoC Container - قسمت 1
LightInject در حال حاضر یکی از قدرتمند‌ترین IoC Container‌‌ها است که از لحاظ سرعت و کارآیی در بالاترین جایگاه در میان IoC Container‌‌های موجود قرار دارد. جهت بررسی کارایی IoC Container‌ها می‌توانید به این لینک مراجعه کنید . LightInject یک IoC Container فوق العاده سبک وزن می‌باشد که تمامی قابلیت‌های متداولی که از یک Service Container انتظار می‌رود را شامل می‌شود. تنها شامل یک فایل .cs می‌باشد که تمامی کدهای آن در همین یک فایل نوشته شده‌اند. در پروژه‌های کوچک تا بزرگ بدون از دست دادن کارآیی، با بالاترین سرعت ممکن عمل تزریق وابستگی را انجام می‌دهد. در این مجموعه مقالات به بررسی کامل این IoC Container می‌پردازیم و تمامی قابلیت‌های آن را آموزش می‌دهیم.

نحوه نصب و راه اندازی LightInject
در پنجره Package Manager Console می‌توانید با نوشتن دستور ذیل، نسخه باینری آن را نصب کنید که به فایل .dll آن Reference میدهد.

PM> Install-Package LightInject
 همچنین می‌توانید توسط دستور ذیل فایل .cs آن را به پروژه اضافه نمایید. 

PM> Install-Package LightInject.Source

 آماده سازی پروژه نمونه 
قبل از شروع کار با LightInject، یک پروژه Windows Forms Application را با ساختار کلاس‌های ذیل ایجاد نمایید. (در مقالات بعدی و پس از آموزش کامل LightInject نحوه استفاده از آن را در ASP.NET MVC نیز آموزش می‌دهیم)
    public class PersonModel
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Family { get; set; }
        public DateTime Birth { get; set; }
    }

    public interface IRepository<T> where T:class
    {
        void Insert(T entity);
        IEnumerable<T> FindAll();
    }

    public interface IPersonRepository:IRepository<PersonModel>
    {
    }

    public class PersonRepository:IPersonRepository
    {
        public void Insert(PersonModel entity)
        {
            throw new NotImplementedException();
        }

        public IEnumerable<PersonModel> FindAll()
        {
            throw new NotImplementedException();
        }
    }

    public interface IPersonService
    {
        void Insert(PersonModel entity);
        IEnumerable<PersonModel> FindAll();
    }

    public class PersonService:IPersonService
    {
        private readonly IPersonRepository _personRepository;

        public PersonService(IPersonRepository personRepository)
        {
            _personRepository = personRepository;
        }

        public void Insert(PersonModel entity)
        {
            _personRepository.Insert(entity);
        }

        public IEnumerable<PersonModel> FindAll()
        {
            return _personRepository.FindAll();
        }
    }
توضیحات
PersonModel: ساختار داده ای جدول Person در سمت Application، که در لایه Domain Model ایجاد می‌گردد.
توجه: جهت سهولت تست و تسریع کدنویسی از لایه بندی و از کلاس‌های ViewModel استفاده نکردیم.
IRepository: یک Interface عمومی برای تمامی Interface‌های مربوط به Repository که عملیات مربوط به پایگاه داده مثل بروزرسانی و واکشی اطلاعات را انجام می‌دهند.
IPersonRepository: واسط بین لایه Service و لایه Repository می‌باشد.
PersonRepository: پیاده سازی واقعی عملیات مربوط به پایگاه داده برای PersonModel می‌باشد. به کلاسهایی که حاوی پیاده سازی واقعی کد می‌باشند Concrete Class می‌گویند.
IPersonService: واسط بین رابط کاربری و لایه سرویس می‌باشد. رابط کاربری به جای دسترسی مستقیم به PersonService از IPersonService استفاده می‌کند.
PersonService: دریافت درخواست‌های رابط کاربری و بررسی قوانین تجاری، سپس ارسال درخواست به لایه Repository در صورت صحت درخواست، و در نهایت ارسال پاسخ دریافتی به رابط کاربری. در واقع واسطی بین Repository و UI می‌باشد.
پس از ایجاد ساختار فوق کد مربوط به Form1 را بصورت زیر تغییر دهید.
public partial class Form1 : Form
    {
        private readonly IPersonService _personService;
        public Form1(IPersonService personService)
        {
            _personService = personService;
            InitializeComponent();
        }
    }
توضیحات
در کد فوق به منظور ارتباط با سرویس از IPersonService استفاده نمودیم که به عنوان پارامتر ورودی برای سازنده Form1 تعریف شده است. حتما با Dependency Inversion و انواع Dependency Injection آشنا هستید که به سراغ مطالعه این مقاله آمدید و علت این نوع کدنویسی را هم می‌دانید. بنابراین توضیح بیشتری در این مورد نمی‌دهم.
حال اگر برنامه را اجرا کنید در Program.cs با خطای عدم وجود سازنده بدون پارامتر برای Form1 مواجه می‌شوید که کد آن را باید به صورت زیر تغییر می‌دهیم.
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            var container = new ServiceContainer();
            container.Register<IPersonService, PersonService>();
            container.Register<IPersonRepository, PersonRepository>();
            Application.Run(new Form1(container.GetInstance<IPersonService>()));
        }
توضیحات
کلاس ServiceContainer وظیفه‌ی Register کردن یک کلاس را برای یک Interface دارد. زمانی که می‌خواهیم Form1 را نمونه سازی نماییم و Application را راه اندازی کنیم، باید نمونه ای را از جنس IPersonService ایجاد نموده و به سازنده‌ی Form1 ارسال نماییم. با رعایت اصل DIP، نمونه سازی واقعی یک کلاس لایه دیگر، نباید در داخل کلاس‌های لایه جاری انجام شود. برای این منظور از شیء container استفاده نمودیم و توسط متد GetInstance، نمونه‌ای از جنس IPersonService را ایجاد نموده و به Form1 پاس دادیم. حال container از کجا متوجه می‌شود که چه کلاسی را برای IPersonService نمونه سازی نماید؟
در خطوط قبلی توسط متد Register، کلاس PersonService را برای IPersonService ثبت نمودیم. container نیز برای نمونه سازی به کلاس هایی که برایش Register نمودیم مراجعه می‌نماید و نمونه سازی را انجام می‌دهد. جهت استفاده از PersonService به پارامتر ورودی IPersonRepository برای سازنده‌ی آن نیاز داریم که کلاس PersonRepository را برای IPersonRepository ثبت کردیم.
حال اگر برنامه را اجرا کنید، به درستی اجرا خواهد شد. برنامه را متوقف کنید و به کد موجود در Program.cs مراجعه نموده و دو خط مربوط به Register را Comment نمایید. سپس برنامه را اجرا کنید و خطای تولید شده را ببینید. این خطا بیان می‌کند که امکان نمونه سازی برای IPersonService را ندارد. چون قبلا هیچ کلاسی را برای آن Register نکرده ایم.
Named Services
در برخی مواقع، بیش از یک کلاس وجود دارند که ممکن است از یک Interface ارث بری نمایند. در این حالت و در زمان Register، باید به ServiceContainer بگوییم که کدام کلاس را باید نمونه سازی نماید. برای بررسی این موضوع، کلاسهای زیر را به ساختار پروژه اضافه نمایید.
    public class WorkerModel:PersonModel
    {
        public ManagerModel Manager { get; set; }
    }

    public class ManagerModel:PersonModel
    {
        public IEnumerable<WorkerModel> Workers { get; set; }
    }

    public class WorkerRepository:IPersonRepository
    {
        public void Insert(PersonModel entity)
        {
            throw new NotImplementedException();
        }

        public IEnumerable<PersonModel> FindAll()
        {
            throw new NotImplementedException();
        }
    }

    public class ManagerRepository:IPersonRepository
    {
        public void Insert(PersonModel entity)
        {
            throw new NotImplementedException();
        }

        public IEnumerable<PersonModel> FindAll()
        {
            throw new NotImplementedException();
        }
    }

    public class WorkerService:IPersonService
    {
        private readonly IPersonRepository _personRepository;

        public WorkerService(IPersonRepository personRepository)
        {
            _personRepository = personRepository;
        }

        public void Insert(PersonModel entity)
        {
            var worker = entity as WorkerModel;
            _personRepository.Insert(worker);
        }

        public IEnumerable<PersonModel> FindAll()
        {
            return _personRepository.FindAll();
        }
    }

    public class ManagerService:IPersonService
    {
        private readonly IPersonRepository _personRepository;

        public ManagerService(IPersonRepository personRepository)
        {
            _personRepository = personRepository;
        }

        public void Insert(PersonModel entity)
        {
            var manager = entity as ManagerModel;
            _personRepository.Insert(manager);
        }

        public IEnumerable<PersonModel> FindAll()
        {
            return _personRepository.FindAll();
        }
    }
توضیحات
دو کلاس Manager و Worker به همراه سرویس‌ها و Repository هایشان اضافه شده اند که از IPersonService و IPersonRepository مشتق شده اند.
حال کد کلاس Program را به صورت زیر تغییر می‌دهیم
...
 var container = new ServiceContainer();
            container.Register<IPersonService, PersonService>();
            container.Register<IPersonService, WorkerService>();
            container.Register<IPersonRepository, PersonRepository>();
            container.Register<IPersonRepository, WorkerRepository>();
            Application.Run(new Form1(container.GetInstance<IPersonService>()));
توضیحات
در کد فوق، چون WorkerService بعد از PersonService ثبت یا Register شده است، LightInject در زمان ارسال پارامتر به Form1، نمونه ای از کلاس WorkerService را ایجاد میکند. اما اگر بخواهیم از کلاس PersonService نمونه سازی نماید باید کد را به صورت زیر تغییر دهیم.
...
            container.Register<IPersonService, PersonService>("PersonService");
            container.Register<IPersonService, WorkerService>();
            container.Register<IPersonRepository, PersonRepository>();
            container.Register<IPersonRepository, WorkerRepository>();
            Application.Run(new Form1(container.GetInstance<IPersonService>("PersonService")));
همانطور که مشاهده می‌نمایید، در زمان Register نامی را به آن اختصاص دادیم که در زمان نمونه سازی از این نام استفاده شده است.
اگر در زمان ثبت، نامی را به نمونه‌ی مورد نظر اختصاص داده باشیم، و فقط یک Register برای آن Interface معرفی نموده باشیم، در زمان نمونه سازی، LightInject آن نمونه را به عنوان سرویس پیش فرض در نظر می‌گیرد.
  container.Register<IPersonService, PersonService>("PersonService");
  Application.Run(new Form1(container.GetInstance<IPersonService>()));
در کد فوق، چون برای IPersonService فقط یک کلاس برای نمونه سازی معرفی شده است، با فراخوانی متد GetInstance، حتی بدون ذکر نام، نمونه ای را از کلاس PersonService ایجاد می‌کند.
IEnumerable<T>
زمانی که چند کلاس را که از یک Interface مشتق شده اند، با هم Register می‌نمایید، LightInject این قابلیت را دارد که این کلاس‌های Register شده را در قالب یک لیست شمارشی برگردانید.
            container.Register<IPersonService, PersonService>();
            container.Register<IPersonService, WorkerService>("WorkerService");
            var personList = container.GetInstance<IEnumerable<IPersonService>>();
در کد فوق لیستی با دو آیتم ایجاد می‌شود که یک آیتم از نوع PersonService و دیگری از نوع WorkerService می‌باشد. همچنین از کد زیر نیز می‌توانید استفاده کنید:
            container.Register<IPersonService, PersonService>();
            container.Register<IPersonService, WorkerService>("WorkerService");
            var personList = container.GetAllInstances<IPersonService>();
به جای متد GetInstance از متد GetAllInstances استفاده شده است.
LightInject از Collection‌های زیر نیز پشتیبانی می‌نماید:
  • Array
  • ICollection<T>
  • IList<T>
  • IReadOnlyCollection<T>
  • IReadOnlyList<T>
Values
توسط LightInject می‌توانید مقادیر ثابت را نیز تعریف کنید
            container.RegisterInstance<string>("SomeValue");
            var value = container.GetInstance<string>();
متغیر value با رشته "SomeValue" مقداردهی می‌گردد. اگر چندین ثابت رشته ای داشته باشید می‌توانید نام جداگانه ای را به هر کدام اختصاص دهید و در زمان فراخوانی مقدار به آن نام اشاره کنید.
            container.RegisterInstance<string>("SomeValue","String1");
            container.RegisterInstance<string>("OtherValue","String2");
            var value = container.GetInstance<string>("String2");
متغیر value با رشته "OtherValue" مقداردهی می‌گردد.
مطالب
بررسی روش مشاهده خروجی SQL حاصل از کوئری‌های Entity framework Core
هنوز تا Entity framework Core 1.1، مفهوم interceptors موجود در EF 6.x پیاده سازی نشده‌است. اما شبیه به مفاهیم «ارتقاء به ASP.NET Core 1.0 - قسمت 17 - بررسی فریم ورک Logging»، در EF Core نیز زیرساختی جهت مشاهده‌ی SQL نهایی تولیدی وجود دارد.


ایجاد یک ثبت کننده‌ی وقایع EF Core

مرحله‌ی اول مشاهده‌ی خروجی‌های نهایی EF Core، پیاده سازی اینترفیس ILoggerProvider است که در آن قرار است وهله‌ی از نوع ILogger بازگشت داده شود. به همین جهت یک کلاس تو در توی خصوصی را در اینجا مشاهده می‌کنید که اینترفیس ILogger را نیز پیاده سازی کرده‌است:
using System;
using Microsoft.Extensions.Logging;

namespace Tests
{
    public class MyLoggerProvider : ILoggerProvider
    {
        public ILogger CreateLogger(string categoryName)
        {
            return new MyLogger();
        }

        public void Dispose()
        { }

        private class MyLogger : ILogger
        {
            public bool IsEnabled(LogLevel logLevel)
            {
                return true;
            }

            public void Log<TState>(
                        LogLevel logLevel, 
                        EventId eventId, 
                        TState state, 
                        Exception exception, 
                        Func<TState, Exception, string> formatter)
            {
                //File.AppendAllText(@"C:\temp\log.txt", formatter(state, exception));
                Console.WriteLine("");
                Console.WriteLine(formatter(state, exception));
            }

            public IDisposable BeginScope<TState>(TState state)
            {
                return null;
            }
        }
    }
}
در اینجا خروجی‌هایی نهایی توسط Console.WriteLine نمایش داده شده‌اند و مناسب برنامه‌های کنسول هستند و یا می‌توان برای مثال توسط File.AppendAllText، اطلاعات رسیده را در یک فایل نیز ذخیره کرد.
در متد Log:
- پارامتر logLevel، سطح اهمیت اطلاعات رسیده را به همراه دارد. برای مثال اطلاعات است یا خطا؟
برای مثال شاید نیاز به ذخیره سازی اطلاعاتی با سطح‌های بحرانی، خطا و یا اخطار در یک بانک اطلاعاتی وجود داشته باشد:
   if (logLevel == LogLevel.Critical || logLevel == LogLevel.Error || logLevel == LogLevel.Warning)
- eventId: نوع رخداد رسیده را مشخص می‌کند.
- state: می‌تواند هر نوع شیءایی، حاوی اطلاعات وضعیت رخداد رسیده باشد.
- exception: بیانگر استثنای احتمالی رخ داده است.
- formatter: کار آن تولید یک رشته‌ی قابل خواندن، توسط اطلاعات حالت و استثناء است.


معرفی Logger تهیه شده به برنامه

پس از تهیه‌ی Logger فوق، جهت معرفی آن به یک برنامه‌ی کنسول، می‌توان به صورت ذیل عمل کرد:
using (var db = new MyContext())
{
   var loggerFactory = (ILoggerFactory)db.GetInfrastructure().GetService(typeof(ILoggerFactory));
   loggerFactory.AddProvider(new MyLoggerProvider());
 }
این ثبت تنها باید یکبار در آغاز برنامه انجام شود و پس از آن تمام وهله‌ی دیگر Context از آن استفاده خواهند کرد.

در برنامه‌های ASP.NET Core، کار معرفی MyLoggerProvider در متد Configure کلاس آغازین برنامه انجام می‌شود:
public void Configure(ILoggerFactory loggerFactory)
{
   loggerFactory.AddProvider(new MyLoggerProvider());


اختصاصی سازی ثبت وقایع رسیده

کلاس MyLoggerProvider، هر نوع اطلاعات داخلی EF Core را نیز لاگ می‌کند. اگر هدف صرفا بررسی خروجی SQL نهایی تولیدی است، می‌توان در متد ذیل:
public ILogger CreateLogger(string categoryName)
بر اساس categoryName رسیده، یا new MyLogger را بازگشت داده و یا یک NullLogger.Instance را که کاری را انجام نمی‌دهد. به این ترتیب می‌توان کار فیلتر کردن اطلاعات رسیده را انجام داد.
برای این منظور، ابتدای Logger تهیه شده چنین شکلی را پیدا می‌کند:
using Microsoft.EntityFrameworkCore.Storage.Internal;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Logging;
using System.Linq;
using System;
 
namespace Tests
{
    public class MyLoggerProvider : ILoggerProvider
    {
        private static readonly string[] _categories =
            {
                typeof(RelationalCommandBuilderFactory).FullName,
                typeof(SqlServerConnection).FullName
            };
        public ILogger CreateLogger(string categoryName)
        {
            if (_categories.Contains(categoryName))
            {
                return new MyLogger();
            }
 
            return NullLogger.Instance;
        }
مطالب
C# 8.0 - Nullable Reference Types
نوع‌های ارجاعی (Reference Types) در #C، همیشه نال‌پذیر بوده‌اند؛ در مقابل نوع‌های مقداری (value types) مانند DateTime که برای نال‌پذیر کردن آن‌ها باید یک علامت سؤال را در حین تعریف نوع آن‌ها ذکر کرد تا تبدیل به یک نوع نال‌پذیر شود (DateTime? Created). بنابراین عنوانی مانند «نوع‌های ارجاعی نال‌نپذیر» شاید آنچنان مفهوم نباشد.
خالق Null در زبان‌های برنامه نویسی، آن‌را یک اشتباه چند میلیارد دلاری می‌داند! و به عنوان یک توسعه دهنده‌ی دات نت، غیرممکن است که در حین اجرای برنامه‌های خود تابحال به null reference exception برخورد نکرده باشید. هدف از ارائه‌ی قابلیت جدید «نوع‌های ارجاعی نال‌نپذیر» در C# 8.0، مقابله‌ی با یک چنین مشکلاتی است و خصوصا غنی سازی IDEها برای ارائه‌ی اخطارهایی پیش از کامپایل برنامه، در مورد قسمت‌هایی از کد که ممکن است سبب بروز null reference exception شوند.


فعالسازی «نوع‌های ارجاعی نال‌نپذیر»

قابلیت «نوع‌های ارجاعی نال‌نپذیر» به صورت پیش‌فرض غیرفعال است. برای فعالسازی آن می‌توان فایل csproj را به صورت زیر، با افزودن خاصیت NullableContextOptions، ویرایش کرد:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <LangVersion>8.0</LangVersion>
    <NullableContextOptions>enable</NullableContextOptions>
  </PropertyGroup>
</Project>
یک نکته: در نگارش‌های بعدی NET Core SDK. و همچنین ویژوال استودیو (از نگارش 16.2.0 به بعد)، خاصیت NullableContextOptions به صرفا Nullable تغییر نام یافته و ساده شده‌است. بنابراین اگر در این نگارش‌ها به خطاهای ذیل برخوردید:
CS8632: The annotation for nullable reference types should only be used in code within a ‘#nullable’ context.
CS8627: A nullable type parameter must be known to be a value-type or non-nullable reference type. Consider adding a ‘class’, ‘struct’ or type constraint.
صرفا به معنای استفاده‌ی از نام قدیمی این ویژگی است که باید به Nullable تغییر پیدا کند:
<PropertyGroup>
  <LangVersion>preview</LangVersion>
  <Nullable>enable</Nullable>
</PropertyGroup>
اما در زمان نگارش این مطلب که 3.0.100-preview5-011568 در دسترس است، فعلا همان نام قدیمی NullableContextOptions کار می‌کند.


تغییر ماهیت نوع‌های ارجاعی #C با فعالسازی NullableContextOptions


در #C ای که ما می‌شناسیم، رشته‌ها قابلیت پذیرش نال را دارند و همچنین ذکر آن‌ها به صورت nullable بی‌معنا است. اما پس از فعالسازی ویژگی نوع‌های ارجاعی نال‌نپذیر، اکنون عکس آن رخ می‌دهد. رشته‌ها نال‌نپذیر می‌شوند؛ اما می‌توان در صورت نیاز، آن‌ها را nullable نیز تعریف کرد.


یک مثال: بررسی تاثیر فعالسازی NullableContextOptions بر روی یک پروژه

کلاس زیر را در نظر بگیرید:
    public class Person
    {
        public string FirstName { get; set; }

        public string MiddleName { get; set; }

        public string LastName { get; set; }

        public Person(string first, string last) =>
            (FirstName, LastName) = (first, last);

        public Person(string first, string middle, string last) =>
            (FirstName, MiddleName, LastName) = (first, middle, last);

        public override string ToString() => $"{FirstName} {MiddleName} {LastName}";
    }
با فعالسازی خاصیت NullableContextOptions، بلافاصله اخطار زیر در IDE ظاهر می‌شود (اگر ظاهر نشد، یکبار پروژه را بسته و مجددا بارگذاری کنید):


در این کلاس، دو سازنده وجود دارند که یکی MiddleName را دریافت می‌کند و دیگری خیر. در اینجا کامپایلر تشخیص داده‌است که چون در سازنده‌ی اولی که MiddleName را دریافت نمی‌کند، مقدار پیش‌فرض خاصیت MiddleName، نال خواهد بود و همچنین ما NullableContextOptions را نیز فعال کرده‌ایم، بنابراین این خاصیت دیگر به صورت معمول و متداول یک نوع ارجاعی نال‌پذیر عمل نمی‌کند و دیگر نمی‌توان نال را به عنوان مقدار پیش‌فرض آن، به آن نسبت داد. به همین جهت اخطار فوق ظاهر شده‌است.
برای رفع این مشکل:
به کامپایلر اعلام می‌کنیم: «می‌دانیم که MiddleName می‌تواند نال هم باشد» و آن‌را در این زمینه راهنمایی می‌کنیم:
public string? MiddleName { get; set; }
پس از این تغییر، اخطار فوق که ذیل سازنده‌ی اول کلاس Person ظاهر شده بود، محو می‌شود. اما اکنون مجددا کامپایلر، در جائیکه می‌خواهیم از آن استفاده کنیم:
    public static class NullableReferenceTypes
    {
        //#nullable enable // Toggle to enable

        public static string Exemplify()
        {
            var vahid = new Person("Vahid", "N");
            var length = GetLengthOfMiddleName(vahid);

            return $"{vahid.FirstName}'s middle name has {length} characters in it.";

            static int GetLengthOfMiddleName(Person person)
            {
                string middleName = person.MiddleName;
                return middleName.Length;
            }
        }
    }
اخطارهایی را صادر می‌کند:


در اینجا در متد محلی (local function) تعریف شده، سعی در دسترسی به خاصیت MiddleName وجود دارد و اکنون با تغییر جدیدی که اعمال کردیم، به صورت نال‌پذیر تعریف شده‌است.
همچنین در سطر بعدی آن نیز نتیجه‌ی نهایی middleName، مورد استفاده قرار گرفته‌است که آن نیز مشکل‌دار تشخیص داده شده‌است.
مشکل اولین سطر را به این صورت می‌توانیم برطرف کنیم:
var middleName = person.MiddleName;
در اینجا بجای ذکر صریح نوع string، از var استفاده شده‌است. پیشتر با ذکر صریح نوع string، آن‌را یک رشته‌ی نال‌نپذیر تعریف کرده بودیم. اما اکنون چون person.MiddleName نال‌پذیر تعریف شده‌است، var نیز به صورت خودکار به این رشته‌ی نال‌پذیر اشاره می‌کند.
اما مشکل سطر دوم هنوز باقی است:


علت اینجا است که متغیر middleName نیز اکنون ممکن است مقدار نال را داشته باشد. برای رفع این مشکل می‌توان از اپراتور .? استفاده کرد و سپس اگر مقدار نهایی این عبارت نال بود، مقدار صفر را بازگشت می‌دهیم:
static int GetLengthOfMiddleName(Person person)
{
   var middleName = person.MiddleName;
   return middleName?.Length ?? 0;
}
هدف از این قابلیت و ویژگی کامپایلر، کمک کردن به توسعه دهنده‌ها جهت نوشتن کدهایی امن‌تر و مقاوم‌تر به null reference exception‌ها است.


امکان خاموش و روشن کردن ویژگی نوع‌های ارجاعی نال‌نپذیر به صورت موضعی

زمانیکه خاصیت NullableContextOptions را فعال می‌کنیم، بر روی کل پروژه تاثیر می‌گذارد. برای مثال اگر یک چنین قابلیتی را بر روی پروژه‌های قدیمی خود فعال کنید، با صدها اخطار مواجه خواهید شد. به همین جهت است که این ویژگی حتی با فعالسازی C# 8.0 و انتخاب آن، به صورت پیش‌فرض غیرفعال است. بنابراین برای اینکه بتوان پروژه‌های قدیمی را قدم به قدم و سر فرصت، «مقاوم‌تر» کرد، می‌توان تعیین کرد که کدام قسمت، تحت تاثیر این ویژگی قرار بگیرد و کدام قسمت‌ها خیر:
public static class NullableReferenceTypes
{
#nullable disable // Toggle to enable
در اینجا می‌توان با استفاده از compiler directive جدید nullable# به کامپایلر اعلام کرد که از این قسمت صرفنظر کن. مقدار آن می‌تواند disable و یا enable باشد.


مجبور ساختن خود به «مقاوم سازی» برنامه

اگر NullableContextOptions را فعال کنید، کامپایلر صرفا یکسری اخطار را در مورد مشکلات احتمالی صادر می‌کند؛ اما برنامه هنوز کامپایل می‌شود. برای اینکه خود را مقید به «مقاوم سازی» برنامه کنیم، می‌توانیم با فعالسازی ویژگی TreatWarningsAsErrors در فایل csprj، این اخطارها را تبدیل به خطای کامپایلر کرده و از کامپایل برنامه جلوگیری کنیم:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <LangVersion>8.0</LangVersion>
    <NullableContextOptions>enable</NullableContextOptions>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
  </PropertyGroup>
</Project>
البته TreatWarningsAsErrors تمام اخطارهای برنامه را تبدیل به خطا می‌کند. اگر می‌خواهید انتخابی‌تر عمل کنید، می‌توان از خاصیت WarningsAsErrors استفاده کرد:
<WarningsAsErrors>CS8600;CS8602;CS8603</WarningsAsErrors>


آیا اگر برنامه‌ای با C# 7.0 کامپایل شود، کتابخانه‌های تهیه شده‌ی با C# 8.0 را می‌تواند استفاده کند؟

پاسخ: بله. از دیدگاه برنامه‌های قدیمی، کتابخانه‌های تهیه شده‌ی با C# 8.0، تفاوتی با سایر کتابخانه ندارند. آن‌ها نوع‌های نال‌پذیر جدید را مانند ?string مشاهده نمی‌کنند؛ آن‌ها فقط string را مشاهده می‌کنند و روش کار کردن با آن‌ها نیز همانند قبل است. بدیهی است در این حالت از مزایای کامپایلر C# 8.0 در تشخیص زود هنگام مشکلات برنامه محروم خواهند بود؛ اما عملکرد برنامه تفاوتی نمی‌کند.


وضعیت برنامه‌ی C# 8.0 ای که از کتابخانه‌های C# 7.0 و یا قبل از آن استفاده می‌کند، چگونه خواهد بود؟

چون کتابخانه‌های قدیمی‌تر از مزایای کامپایلر C# 8.0 استفاده نمی‌کنند، خروجی‌های آن بدون بروز خطایی توسط کامپایلر C# 8.0 استفاده می‌شوند؛ چون حجم اخطارهای صادر شده‌ی در این حالت بیش از حد خواهد بود. یعنی این بررسی‌های کامپایلر صرفا برای کتابخانه‌های جدید فعال هستند و نه برای کتابخانه‌های قدیمی.


مهارت‌های مواجه شدن با اخطارهای ناشی از فعالسازی NullableContextOptions

در مثالی که بررسی شد، یک نمونه از روش‌های مواجه شدن با اخطارهای ناشی از فعالسازی ویژگی نوع‌های ارجاعی نال‌نپذیر را بررسی کردیم. در ادامه روش‌های تکمیلی دیگری را بررسی می‌کنیم.

1- هرجائیکه قرار است متغیر ارجاعی نال‌پذیر باشد، آن‌را صراحتا اعلام کنید.
string name = null; // ERROR
string? name = null; // OK!
این مثال را پیشتر بررسی کردیم. با فعالسازی ویژگی نوع‌های ارجاعی نال‌نپذیر، ماهیت آن‌ها نیز تغییر می‌کند و دیگر نمی‌توان به آن‌ها null را انتساب داد. اگر نیاز است حتما اینکار صورت گیرد، آن‌ها را توسط ? به صورت nullable تعریف کنید.
نمونه‌ی دیگر آن مثال زیر است:
public class Person
{
    public Address? Address { get; set; };
    public string Country => Address?.Country;   // ERROR! 
}
در اینجا Address یک نوع ارجاعی نال‌پذیر است. بنابراین حاصل Address?.Country می‌تواند نال باشد و به Country نال‌نپذیر قابل انتساب نیست. برای رفع این مشکل کافی است دقیقا مشخص کنیم که این رشته نیز نال‌پذیر است:
public class Person
{
    public Address? Address { get; set; };
    public string? Country => Address?.Country;  // OK!
}

البته در این حالت باید به مثال زیر دقت داشت:
var node = this; // Initialize non-nullable variable
while (node != null)
{
   node = null; // ERROR!
}
چون node در اینجا توسط var تعریف شده‌است، دقیقا نوع this را که non-nullable است، پیدا می‌کند. بنابراین بعدها نمی‌توان به آن null را انتساب داد. اگر چنین موردی نیاز بود، باید صریحا نوع آن‌را بدو امر، nullable تعریف کرد؛ چون هنوز امکان تعریف ?var میسر نیست:
Node? node = this;   // Initialize nullable variable
while (node != null) {
   node = null; // OK!
}


2- نوع‌های خود را مقدار دهی اولیه کنید.
در مثال زیر:
public class Person
{
   public string Name { get; set; } // ERROR!
}
در این حالت چون خاصیت Name، در سازنده‌ی کلاس مقدار دهی اولیه نشده‌است، یک اخطار صادر می‌شود که بیانگر احتمال نال بودن آن است. یک روش مواجه شدن با این مشکل، تعریف آن به صورت یک خاصیت نال‌پذیر است:
public class Person
{
   public string? Name { get; set; }
}

یا یک استثناء را صادر کنید:
public class Person
{
    public string Name { get; set; }
    public Person(string name) {
        Name = name ?? throw new ArgumentNullException(nameof(name));
    }
}
به این ترتیب کامپایلر می‌داند که اگر نام دریافتی نال بود، دقیقا باید چگونه رفتار کند.
البته در این حالت برای مقدار دهی اولیه‌ی Name، حتما نیاز به تعریف یک سازنده‌است و در این حالت کدهایی را که از سازنده‌ی پیش‌فرض استفاده کرده بودند (مانند new Person { Name = "Vahid" })، باید تغییر دهید.

راه‌حل دیگر، مقدار دهی اولیه‌ی این خواص بدون تعریف یک سازنده‌ی اضافی است:
public class Person
{
   public string Name { get; set; } = string.Empty;
   // -or-
   public string Name { get; set; } = "";
}
برای مثال می‌توان از مقادیر خالی زیر برای مقدار دهی اولیه‌ی رشته‌ها، آرایه‌ها و مجموعه‌ها استفاده کرد:
String.Empty
Array.Empty<T>()
Enumerable.Empty<T>()
یا حتی می‌توان اشیاء دیگر را نیز به صورت زیر مقدار دهی اولیه کرد:
public class Person
{
   public Address Address { get; set; } = new Address();
}
البته در این حالت باید مفهوم فلسفی «خالی بودن» را پیش خودتان تفسیر و تعریف کنید که دقیقا مقصود از یک آدرس خالی چیست؟ به همین جهت شاید تعریف این شیء به صورت nullable بهتر باشد.
مطالب
تنظیمات امنیتی Glimpse
در مورد glimpse پیشتر مطالبی در سایت منتشر شده است :
 آشنایی و بررسی ابزار Glimpse 
بعد از آپلود سایت ما می‌توانیم دسترسی به تنظیمات خاص glimpse را تنها به کاربران عضو محدود کنیم:
<location path="Glimpse.axd" >
    <system.web>
        <authorization>
            <allow users="Administrator" />
            <deny users="*" />
        </authorization>
    </system.web>
</location>

یا می‌توانیم آنرا غیرفعال کنیم :
<glimpse defaultRuntimePolicy="Off" xdt:Transform="SetAttributes">
</glimpse>

همچنین می‌توانیم با پیاده سازی اینترفیس IRuntimePolicy سیاست‌های مختلف نمایش تب‌های glimpse را تعیین کنیم :
using Glimpse.AspNet.Extensions;
using Glimpse.Core.Extensibility;

namespace Test
{
    public class GlimpseSecurityPolicy:IRuntimePolicy
    {
        public RuntimePolicy Execute(IRuntimePolicyContext policyContext)
        {
            // You can perform a check like the one below to control Glimpse's permissions within your application.
// More information about RuntimePolicies can be found at http://getglimpse.com/Help/Custom-Runtime-Policy
var httpContext = policyContext.GetHttpContext();
            if (!httpContext.User.IsInRole("Administrator "))
            {
                return RuntimePolicy.Off;
            }

            return RuntimePolicy.On;
        }

        public RuntimeEvent ExecuteOn
        {
            get { return RuntimeEvent.EndRequest; }
        }
    }
}

زمانیکه glimpse را از طریق Nuget نصب می‌کنید کلاس فوق به صورت اتوماتیک به پروژه اضافه می‌شود با این تفاوت که به صورت کامنت شده است تنها کاری شما باید انجام بدید کدهای فوق را از حالت کامنت خارج کنید و Role مربوطه را جایگزین کنید. 

نکته : کلاس فوق نیاز به رجیستر شدن ندارد و تشخیص آن توسط Glimpse به صورت خودکار انجام می‌شود.  
نظرات مطالب
شروع به کار با EF Core 1.0 - قسمت 10 - استفاده از امکانات بومی بانک‌های اطلاعاتی
چگونه می‌توان در تزریق وابستگی به استفاده از unitofwork ویو BlogPostCounts را صدا زد؟ و مقدار آن را خواند
شبیه این روند
        private readonly IUnitOfWork _uow;
        private readonly DbSet<Student> _student;

        public StudentService(IUnitOfWork uow)
        {
            _uow = uow;
            _uow.CheckArgumentIsNull(nameof(_uow));

            _student = _uow.Set<Student>();
        }