نظرات مطالب
بررسی روش آپلود فایل‌ها در ASP.NET Core
اگر در Model پروپرتی Photo از جنس string باشد و در ViewModel این پروپرتی رو برای استفاده از امکانات validation و غیره به IFormFile تغییر بدیم. برای ارسال آدرس فایل به مدل که از طریق viewmodel تغذیه میشه، آیا نیاز به تعریف یک پروپرتی دیگر از جنس string  در viewmodel داریم؟ یا اینکه روش دیگری برای این کار وجود دارد؟
 public class Model
    {
        public string Photo { get; set; }        
    }

 public class viewModel
    {
        public IFormFile Photo { get; set; }
    }
مطالب
C# 12.0 - Primary Constructors
قابلیتی تحت عنوان Primary Constructors به C# 12 اضافه شده‌است که ... البته جدید نیست! این قابلیت از زمان C# 9، با ارائه‌ی رکوردها، به زبان #C اضافه شد و در طی چند نگارش بعدی، توسعه و تکامل یافت (برای مثال اضافه شدن records for structs به C# 10) تا در C# 12، به کلاس‌های معمولی نیز تعمیم پیدا کرد. این ویژگی را در ادامه با جزئیات بیشتری بررسی می‌کنیم.


Primary Constructors چیست؟

Primary Constructors، قابلیتی است که به C# 12 اضافه شده‌است تا توسط آن بتوان خواص را مستقیما توسط پارامترهای سازنده‌ی یک کلاس تعریف و همچنین مقدار دهی کرد. هدف از آن، کاهش قابل ملاحظه‌ی یکسری کدهای تکراری و مشخص است تا به کلاس‌هایی زیباتر، کم‌حجم‌تر و خواناتر برسیم. برای مثال کلاس متداول زیر را درنظر بگیرید:
public class Employee
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime HireDate { get; set; }
    public decimal Salary { get; set; }

    public Employee(string firstName, string lastName, DateTime hireDate, decimal salary)
    {
        FirstName = firstName;
        LastName = lastName;
        HireDate = hireDate;
        Salary = salary;
    }
}
در زبان ‍#C، سازنده، متد ویژه‌ای است که در حین ساخت نمونه‌ای از یک کلاس، فراخوانی می‌شود. هدف از آن‌، آغاز و مقدار دهی حالت شیء ایجاد شده‌است که عموما با مقدار دهی خواص آن شیء، انجام می‌شود.
اکنون اگر بخواهیم همین کلاس را با استفاده از ویژگی Primary Constructor اضافه شده به C# 12.0 بازنویسی کنیم، به قطعه کد زیر می‌رسیم:
public class Employee(string firstName, string lastName, DateTime hireDate, decimal salary)
{
    public string FirstName { get; set; } = firstName;
    public string LastName { get; set; } = lastName;
    public DateTime HireDate { get; set; } = hireDate;
    public decimal Salary { get; set; } = salary;
}
و نحوه‌ی نمونه سازی از آن به صورت زیر است:
var employee = new Employee("John", "Doe", new DateTime(2020, 1, 1), 50000);

یک نکته: اگر از Rider و یا ReSharper استفاده می‌کنید، یک چنین Refactoring توکاری جهت سهولت کار، به آن‌ها اضافه شده‌است و به سرعت می‌توان این تبدیلات را توسط آن‌ها انجام داد.




توضیحات:
- متد سازنده در این حالت، به ظاهر حذف شده و به قسمت تعریف کلاس منتقل شده‌است.
- تمام مقدار دهی‌های آغازین موجود در متد سازنده‌ی پیشین نیز حذف شده‌اند و مستقیما به قسمت تعریف خواص، منتقل شده‌اند.
در نتیجه از یک کلاس 15 سطری، به کلاسی 7 سطری رسیده‌ایم که کاهش حجم قابل ملاحظه‌ای را پیدا کرده‌است.

نکته 1: هیچ ضرورتی وجود ندارد که به همراه یک primary constructor، خواصی هم مانند مثال فوق ارائه شوند؛ چون پارامترهای آن در تمام اعضای این کلاس، به همین شکل، قابل دسترسی هستند. در این مثال صرفا جهت بازسازی کد قبلی، این خواص اضافی را مشاهده می‌کنید. یعنی اگر تنها قرار بود، کار تزریق وابستگی‌ها صورت گیرد که عموما به همراه تعریف فیلدهایی جهت انتساب پارامترهای متد سازنده به آن‌ها است، استفاده از یک primary constructor، کدهای فوق را بیش از این هم فشرده‌تر می‌کرد و ... یک سطری می‌شد.

نکته 2: استفاده از پارامترهای سازنده‌ی اولیه، صرفا جهت مقدار دهی خواص عمومی یک کلاس، یک code smell هستند! چون می‌توان یک چنین کارهایی را به نحو شکیل‌تری توسط required properties معرفی شده‌ی در C# 11، پیاده سازی کرد.


بررسی تاریخچه‌ی primary constructors

همانطور که در مقدمه‌ی بحث نیز عنوان شد، primary constructors قابلیت جدیدی نیست و برای نمونه به همراه C# 9 و مفهوم جدید رکوردهای آن، ارائه شد:
public record class Book(string Title, string Publisher);
مثال فوق که به positional syntax هم معروف است، به همراه بکارگیری primary constructors است. در اینجا کامپایلر به صورت خودکار، کار تولید کدهای خواص متناظر را که از نوع get و init دار هستند، انجام می‌دهد. در این حالت به علت استفاده از init accessors، پس از نمونه سازی شیءای از آن، دیگر نمی‌توان مقدار خواص متناظر را تغییر داد.
پس از آن در C# 10، این توسعه ادامه یافت و به امکان تعریف record structها، بسط یافت که در اینجا هم قابلیت تعریف primary constructors وجود دارد:
public record struct Color(int R, int G, int B);
که البته در این حالت برخلاف record classها، کامپایلر، کدی را که برای خواص تولید می‌کند، get و set دار است. در اینجا اگر نیاز است به همان حالت خواص get و init دار رسید، می‌توان یک readonly record struct را تعریف کرد.

پس از این مقدمات، اکنون در C# 12 نیز می‌توان primary constructors را به تمام کلاس‌ها و structهای معمولی هم اعمال کرد؛ با این تفاوت که در اینجا برخلاف رکوردها، کدهای خواص‌های متناظر، به صورت خودکار تولید نمی‌شوند و اگر به آن‌ها نیاز دارید، باید آن‌ها را همانند مثال ابتدای بحث، خودتان به صورت دستی تعریف کنید.


primary constructors کلاس‌ها و structهای معمولی، با primary constructors رکوردها یکی نیست

در C# 12 و به همراه معرفی primary constructors مخصوص کلاس‌ها و structهای معمولی آن، از روش متفاوتی برای دسترسی به پارامترهای تعریف شده، استفاده می‌کند که به آن capturing semantics هم می‌گویند. در این حالت پارامترهای تعریف شده‌ی در یک primary constructor، توسط هر عضوی از آن کلاس قابل استفاده‌است که یکی از کاربردهای آن، ساده کردن تعاریف تزریق وابستگی‌ها است. در این حالت دیگر نیازی نیست تا ابتدا یک فیلد را برای انتساب به پارامتر تزریق شده تعریف کرد و سپس از آن فیلد، استفاده نمود؛ مستقیما می‌توان با همان پارامتر تعریف شده، در متدها و اعضای کلاس، کار کرد.
برای مثال سرویس زیر را که از تزریق وابستگی‌ها، در سازنده‌ی خود استفاده می‌کند، درنظر بگیرید:
public class MyService
{
    private readonly IDepedent _dependent;
  
    public MyService(IDependent dependent)
    {
        _dependent = dependent;
    }
  
    public void Do() 
    {
        _dependent.DoWork();
    }
}
این کلاس در C# 12 به صورت زیر خلاصه شده و پارامتر dependent تعریف شده‌ی در سازنده‌ی اولیه‌ی آن، به همان شکل و بدون نیاز به کد اضافی، در سایر متدهای این کلاس قابل استفاده‌است:
public class MyService(IDependent dependent)
{
    public void Do() 
    {
        dependent.DoWork();
    }
}

البته مفهوم Captures هم در زبان #C جدید نیست و در ابتدا به همراه anonymous methods و بعدها به همراه lambda expressions، معرفی و بکار گرفته شد. برای مثال درون یک lambda expression، اگر از متغیری خارج از آن lambda expressions استفاده شود، کامپایلر یک capture از آن متغیر را تهیه کرده و استفاده می‌کند.

بنابراین به صورت خلاصه primary constructors در رکوردها، با هدف تعریف خواص عمومی فقط خواندنی، ارائه شدند؛ اما primary constructors ارائه شده‌ی در C# 12 که اینبار قابل اعمال به کلاس‌ها و structs معمولی است، بیشتر هدف ساده سازی تعریف کدهای تکراری private fields را دنبال می‌کند. برای نمونه این کدی است که کامپایلر برای primary constructor مثال ابتدای بحث تولید می‌کند و در اینجا نحوه‌ی تولید خودکار این فیلدهای خصوصی را مشاهده می‌کنید:
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace CS8Tests
{
  [NullableContext(1)]
  [Nullable(0)]
  public class Employee
  {
    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private string <FirstName>k__BackingField;
    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private string <LastName>k__BackingField;
    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private DateTime <HireDate>k__BackingField;
    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private Decimal <Salary>k__BackingField;

    public Employee(string firstName, string lastName, DateTime hireDate, Decimal salary)
    {
      this.<FirstName>k__BackingField = firstName;
      this.<LastName>k__BackingField = lastName;
      this.<HireDate>k__BackingField = hireDate;
      this.<Salary>k__BackingField = salary;
      base..ctor();
    }

    public string FirstName
    {
      [CompilerGenerated] get
      {
        return this.<FirstName>k__BackingField;
      }
      [CompilerGenerated] set
      {
        this.<FirstName>k__BackingField = value;
      }
    }

    public string LastName
    {
      [CompilerGenerated] get
      {
        return this.<LastName>k__BackingField;
      }
      [CompilerGenerated] set
      {
        this.<LastName>k__BackingField = value;
      }
    }

    public DateTime HireDate
    {
      [CompilerGenerated] get
      {
        return this.<HireDate>k__BackingField;
      }
      [CompilerGenerated] set
      {
        this.<HireDate>k__BackingField = value;
      }
    }

    public Decimal Salary
    {
      [CompilerGenerated] get
      {
        return this.<Salary>k__BackingField;
      }
      [CompilerGenerated] set
      {
        this.<Salary>k__BackingField = value;
      }
    }
  }
}
بنابراین آیا پارامترهای سازنده‌ی اولیه، به صورت خواص تعریف می‌شوند و قابلیت تغییر میدان دید آن‌ها میسر است؟ پاسخ: خیر. این پارامترها توسط کامپایلر، به صورت فیلدهای خصوصی در سطح کلاس، تعریف و استفاده می‌شوند. یعنی تمام اعضای کلاس، البته منهای سازنده‌های ثانویه، به این پارامترها دسترسی دارند. همچنین، این تولید کد هم بهینه‌است و صرفا برای پارامترهایی انجام می‌شود که واقعا در کلاس استفاده شده باشند؛ درغیر اینصورت، فیلد خصوصی متناظری برای آن‌ها تولید نخواهد شد.

یک نکته: برای مشاهده‌ی یک چنین کدهایی می‌توانید از منوی Tools->IL Viewer برنامه‌ی Rider استفاده کرده و در برگه‌ی ظاهر شده، گزینه‌ی #Low-Level C آن‌را انتخاب نمائید.


امکان تعریف سازنده‌های دیگر، به همراه سازنده‌ی اولیه

اگر به کدهای #Low-Level C تولیدی فوق دقت کنید، این کلاس، به همراه یک سازنده‌ی خالی بدون پارامتر (parameter less constructor) نیست و سازنده‌ی پیش‌فرضی (default constructor) برای آن درنظر گرفته نشده‌است ... اما اگر کلاسی به همراه یک primary constructor تعریف شد، می‌توان با استفاده از واژه‌ی کلیدی this، سازنده‌ی ثانویه‌ای را هم برای آن تعریف کرد:
public class Person(string firstName, string lastName) 
{
    public Person() : this("John", "Smith") { }
    public Person(string firstName) : this(firstName, "Smith") { }
    public string FullName => $"{firstName} {lastName}";
}
در اینجا نحوه‌ی تعریف یک Default constructor بدون پارامتر را هم ملاحظه می‌کنید.


امکان ارث‌بری و تعریف سازنده‌ی اولیه

مثال زیر را درنظر بگیرید که در آن کلاس مشتق شده‌ی از کلاس User، یک سازنده‌ی اولیه را تعریف کرده:
public class User
{
    public User(string firstName, string lastName) { }
}

public class Editor(string firstName, string lastName) : User
{
}
در این حالت برنامه با خطای «Base class 'CS8Tests.User' does not contain parameterless constructor» کامپایل نمی‌شود. عنوان می‌کند که اگر کلاس مشتق شده می‌خواهد سازنده‌ی اولیه‌ای داشته باشد، باید کلاس پایه را به همراه یک سازنده‌ی پیش‌فرض بدون پارامتر تعریف کنید.
البته این محدودیت با structها وجود ندارد؛ چون structها، value type هستند و همواره به صورت پیش‌فرض، به همراه یک سازنده‌ی پیش فرض بدون پارامتر، تولید می‌شوند.
یک مثال: قطعه کد متداول ارث‌بری زیر را درنظر بگیرید که در آن، کلاس مشتق شده به کمک واژه‌ی کلید base، امکان تعریف سازنده‌ی جدیدی را یافته و یکی از پارامترهای سازنده‌ی کلاس پایه را مقدار دهی می‌کند:
public class Automobile
{
    public Automobile(int wheels, int seats)
    {
        Wheels = wheels;
        Seats = seats;
    }

    public int Wheels { get; }
    public int Seats { get; }
}

public class Car : Automobile
{
    public Car(int seats) : base(4, seats)
    {
    }
}
این تعاریف در C# 12 به صورت زیر خلاصه می‌شوند:
public class Automobile(int wheels, int seats)
{
    public int Wheels { get; } = wheels;
    public int Seats { get; } = seats;
}

public class Car(int seats) : Automobile(4, seats);

و یا یک نمونه مثال دیگر آن به صورت زیر است که در آن، ذکر بدنه‌ی کلاس در C# 12، الزامی ندارد:
public class MyBaseClass(string s); // no body required

public class Derived(int i, string s, bool b) : MyBaseClass(s)
{
    public int I { get; set; } = i;
    public string B => b.ToString();
}


توصیه به پرهیز از double capturing

با مفهوم capture در این مطلب آشنا شدیم. در مثال زیر دوبار از پارامتر سازنده‌ی age، در دو قسمت عمومی شده، استفاده شده‌است:
public class Human(int age)
{
    // initialization
    public int Age { get; set; } = age;

    // capture
    public string Bio => $"My age is {age}!";
}
در این حالت ممکن است استفاده کننده در طول برنامه، با وضعیت ناخواسته‌ی زیر مواجه شود:
var p = new Human(42);
Console.WriteLine(p.Age); // Output: 42
Console.WriteLine(p.Bio); // Output: My age is 42!

p.Age++;
Console.WriteLine(p.Age); // Output: 43
Console.WriteLine(p.Bio); // Output: My age is 42! // !
در اینجا پس از افزودن مقداری به خاصیت عمومی Age، زمانیکه به مقدار عبارت Bio مراجعه می‌شود، خروجی قبلی را دریافت می‌کنیم!
درک بهتر آن، نیاز به #Low-Level C کلاس Human را دارد:
using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace CS8Tests
{
  [NullableContext(1)]
  [Nullable(0)]
  public class Human
  {
    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private int <age>P;
    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private int <Age>k__BackingField;

    public Human(int age)
    {
      this.<age>P = age;
      this.<Age>k__BackingField = this.<age>P;
      base..ctor();
    }

    public int Age
    {
      [CompilerGenerated] get
      {
        return this.<Age>k__BackingField;
      }
      [CompilerGenerated] set
      {
        this.<Age>k__BackingField = value;
      }
    }

    public string Bio
    {
      get
      {
        DefaultInterpolatedStringHandler interpolatedStringHandler = new DefaultInterpolatedStringHandler(11, 1);
        interpolatedStringHandler.AppendLiteral("My age is ");
        interpolatedStringHandler.AppendFormatted<int>(this.<age>P);
        interpolatedStringHandler.AppendLiteral("!");
        return interpolatedStringHandler.ToStringAndClear();
      }
    }
  }
}
همانطور که مشاهده می‌کنید، کامپایلر، پارامتر age را دوبار، جداگانه capture کرده‌است:
public Human(int age)
{
   this.<age>P = age;
   this.<Age>k__BackingField = this.<age>P;
   base..ctor();
}
به همین جهت است که ++p.Age، فقط بر روی یکی از فیلدهای capture شده تاثیر داشته و بر روی دیگری خیر. به این مورد، double capturing گفته می‌شود و توصیه شده از آن پرهیز کنید و بجای استفاده‌ی دوباره از پارامتر age، از خود خاصیت Age استفاده نمائید.
نظرات مطالب
مقابله با XSS ؛ یکبار برای همیشه!
سلام 
من یک سوال داشتم . من از کلاس‌های شما در برنامه ام استفاده کردم مدل من به شرح زیر است :
  public class Contact
    {
        public int Id { get; set; }
        [AllowHtml]
        public string Name { get; set; }
        public string Address { get; set; }
    }
و برای تست اکشن‌های زیر رو داخل کنترلر نوشتم :
 [HttpGet]
        public ActionResult ContactUs()
        {
            return View();
        }
        [HttpPost]
        public ActionResult ContactUs(Contact con)
        {

            ViewBag.Name = con.Name.ToSafeHtml();
            ViewBag.MyAddress = con.Address;
            return View();
        }

خوب همه چیز به درستی کار می‌کنه . اما سوالات من به شرح زیر است :
۱ - آیا واقعا نیاز نیست پروپرتی هایی رو که AllowHTML نیستند رو کنترل کنیم ؟ خود Asp.net MVC جلوی این قضیه رو می‌گیره ؟
۲ - اگر ما یک پروپرتی رو AllowHtml نگذاریم و قصد وارد کردن یک تگ Script داشته باشیم با خطای زیر مواجه می‌شویم :
A potentially dangerous Request.Form value was detected from the client (Name="<script>alert("aaa")...").
چطور این پیام رو سیستم نشون نده و یک پیام سفارشی نشون بده ؟
۳ - از متد‌های دیگه ای که در کلاس‌های شما است در کجا‌ها با مثال عملیاتی می‌شه استفاده کرد ؟
تشکر از لطفتون
مطالب
اصول طراحی شی‌ء گرا: OO Design Principles - قسمت دوم

اصل چهارم: Starve for loosely coupled designs

"به دنبال طراحی با اتصال سست بین اجزا باش"

اتصال بین اجزای برنامه نویسی باعث سخت‌تر شدن مدیریت تغییرات می‌شود؛ چرا که با تغییر یک بخش، بخش‌های متصل نیز دچار مشکل خواهند شد. اتصال‌ها از لحاظ نوع قدرت متفاوتند و اساسا سیستمی بدون اتصال وجود ندارد. لذا باید به دنبال یک طراحی با کمترین میزان قدرت اتصال یا همان سست اتصال باشیم.

تا به اینجا، اصل‌های دوم و سوم ما را در کاهش وابستگی و اتصال قوی کمک کرده‌اند. استفاده از واسط‌ها، باعث کاهش وابستگی به نوع پیاده سازی می‌شود. استفاده از ترکیب نیز به نوعی باعث از بین رفتن وابستگی قوی بین کلاس‌های فرزند و کلاس والد می‌شود و با روشی دیگر (استفاده از شیء در برگرفته شده برای پیاده سازی وظیفه‌ی تغییر کننده) وظایف را در کلاس‌ها پیاده سازی میکند. در زیر نمونه‌ی اتصال قوی و نتیجه‌ی آن را می‌بینیم: 

public class StrongCoupledConcreteA
    {
        public string GenerateString(string s) { return s + " from" + this.GetType().ToString(); }
    }

    public class StrongCoupledConcreteB
    {
        public void GenerateString(ref string s) { s += " from" + this.GetType().ToString(); }
    }

    public class Printer
    {
        bool condition;
        public Printer(bool cond)
        {
            condition = cond;
        }

        public void SetCondition(bool value) { condition = value; }

        public void Print()
        {
            string result;
            string input = " this message is";
            if (condition)
            {
                var stringGenerator = new StrongCoupledConcreteA();
                result = stringGenerator.GenerateString(input);
            }
            else
            {
                var stringGenerator = new StrongCoupledConcreteB();
                result = input;
                stringGenerator.GenerateString(ref result);
            }
            Console.WriteLine(result);
        }

    }
    public class Context
    {
        Printer printer;
        public void DoWork()
        {
            printer = new Printer(true);
            printer.Print();

            printer.SetCondition(false);
            printer.Print();
        }

    }

حال کد بازنویسی شده را با آن مقایسه کنید:

public interface IStringGenerator
        {
            string GenerateString(string s);
        }
        public class LooslyCoupledConcreteA : IStringGenerator
        {
            public string GenerateString(string s)
            {
                return s + " from " + this.GetType().ToString();
            }
        }
        public class LooslyCoupledConcreteB : IStringGenerator
        {
            public string GenerateString(string s)
            {
                return s + " from " + this.GetType().ToString();
            }
        }

           public class Printer
           {
               bool condition;
               public Printer(bool cond)
               {
                   condition = cond;
               }

               public void SetCondition(bool value) { condition = value; }

               public void Print()
               {
                   string result;
                   string input = " this message is";
                   IStringGenerator generator;
                   if (condition)
                   {
                       generator = new LooslyCoupledConcreteA();
                   }
                   else
                   {
                       generator = new LooslyCoupledConcreteB();
                   }
                   
                   result = generator.GenerateString(input);
                   Console.WriteLine(result);

               }

           }

با کمی دقت مشاهده میکنیم که در کلاس‌های strongly coupled با اینکه هدف هر دو کلاس تولید یک رشته است، ولی عدم وجود پروتکل باعث شده است نحوه‌ی گرفتن ورودی و برگرداندن خروجی متفاوت شود و در نتیجه نیازمند به اضافه کردن پیچیدگی در کلاس فراخوانی کننده‌ی آن‌ها می‌شویم. این در حالی است که در روش loosely coupled با ایجاد یک پروتکل (واسط IStringGenerator ) این پیچیدگی از بین رفته است. در اینجا نوع اتصال (وابستگی) از جنس اتصال (وابستگی) قوی به تعریف (prototype) و شاید به نوعی نحوه‌ی پیاده سازی متد می‌باشد.


SOLID Principles *

پنج اصل بعدی به اصول SOLID معروف هستند.

S: Single Responsibility

O: Open/Closed

L: Liskov’s Substitution

I: Interface Segregation

D: Dependency Injection


اصل پنجم: Single responsibility

"به دنبال ماژول‌های تک مسئولیتی باش"

در این قسمت مقصود از مسئولیت، «دلیلی است که کلاس باید تغییر کند» بدین معنا که اگر کلاسی با چند دلیل متفاوت مجبور به تغییر شود، آن کلاس چند مسئولیتی است. کلاس‌های چند مسئولیتی عموما کد حجیمی دارند؛ نام آنها تعریف دقیقی را از مسئولیتشان ارائه نمی‌دهد و با عنوانی بسیار کلی نامگذاری میشوند و اشکال زدایی آنها بسیار طاقت فرساست. از طرفی، چند مسئولیتی بودن یک کلاس، باعث از بین رفتن مزایای توارث می‌شود. مثلا فرض کنید دو مسئولیت A,B در واسطی بیان می‌شوند که به یکدیگر مرتبط نبوده و مستقلند. برای  مسئولیت A دو پیاده سازی و برای مسئولیت B،   سه پیاده سازی در نظر گرفته شده است و جمعا برای پشتیبانی از تمامی حالات باید شش کلاس پیاده ساز، در نظر گرفته شود که  توارث را سخت و بی معنی میکند زیرا قابلیت استفاده مجدد را از توارث سلب کرده است. با این وجود عملا رعایت همچین نکته‌ای در دنیای واقعی کار سختی است.

مثال زیر این مشکل را بیان می‌دارد: 

// single responsibility principle - bad example

    interface IEmail
    {
        void SetSender(string sender);
        void SetReceiver(string receiver);
        void SetContent(string content);
    }

    class Email : IEmail
    {
        public void SetSender(string sender)
        {
            throw new NotImplementedException();
        }
        public void SetReceiver(string receiver)
        {
            throw new NotImplementedException();
        }

        public void SetContent(string content)
        {
            throw new NotImplementedException();
        }
    }

در این مثال کلاس Email دارای دو مسئولیت (دلیل برای تغییر) است: الف- نحوه مقداردهی فرستنده و گیرنده براساس پروتکل‌های مختلف مانند IMAP, POP3 ، بدین معنا که با تغییر پروتکل نیاز به تغییر پیاده سازی خواهیم شد. ب- تعریف محتوای پیام، بدین معنا که برای پشتیبانی از محتوای html, xml   نیاز به تغییر کلاس Email داریم.

با تغییر طراحی خواهیم داشت: 

// single responsibility principle - good example
    public interface IMessage
    {
        void SetSender(string sender);
        void SetReceiver(string receiver);
        void SetContent(IContent content);
    }

    public interface IContent
    {
        string GetAsString(); // used for serialization
    }

    public class Email : IMessage
    {        
        public void SetSender(string sender)
        {
            throw new NotImplementedException();
        }

        public void SetReceiver(string receiver)
        {
            throw new NotImplementedException();
        }

        public void SetContent(IContent content)
        {
            throw new NotImplementedException();
        }
    }

در اینجا واسط IContent مسئولیت پشتیبانی از xml, html را خواهد داشت و نیازی به تغییر کلاس Email برای پشتیبانی از این فرمت‌های محتوای پیام را نخواهیم داشت.


اصل ششم: Open for extension, close for modification :  Open/Closed Principle

"پذیرای توسعه و بازدارنده از تغییر هر آنچه که هست، باش"

ا ین اصل می‌گوید طراحی باید به گونه‌ای باشد که با اضافه شدن یک ویژگی، کد‌های قبلی تغییری نکنند و فقط کدهای جدید برای پیاده سازی ویژگی جدید نوشته شوند.  برای درک بهتر به مثال زیر توجه کنید:

public class AreaCalculator
        {
            public double Area(object[] shapes)
            {
                double area = 0;

                foreach (var shape in shapes)
                {

                    if (shape is Square)
                    {
                        Square square = (Square)shape;
                        area += Math.Sqrt(square.Height);
                    }

                    if (shape is Triangle)
                    {
                        Triangle triangle = (Triangle)shape;
                        double TotalHalf = (triangle.FirstSide + triangle.SecondSide + triangle.ThirdSide) / 2;
                        area += Math.Sqrt(TotalHalf * (TotalHalf - triangle.FirstSide) *
                        (TotalHalf - triangle.SecondSide) * (TotalHalf - triangle.ThirdSide));
                    }

                    if (shape is Circle)
                    {
                        Circle circle = (Circle)shape;
                        area += circle.Radius * circle.Radius * Math.PI;
                    }

                }
                return area;
            }
        }
        public class Square
        {
            public double Height { get; set; }
        }
        public class Circle
        {
            public double Radius { get; set; }
        }
        public class Triangle
        {
            public double FirstSide { get; set; }
            public double SecondSide { get; set; }
            public double ThirdSide { get; set; }
        }

در اینجا کلاس AreaCalculator برای محاسبه مساحت تمام اشیاء ورودی، مساحت تک تک اشیاء را محاسبه میکند و نتیجه را برمی‌گرداند. در این مثال با اضافه شدن شکل هندسی جدید، باید کد این کلاس تغییر کند که با اصل Open/Closed مغایر است. برای بهبود این کد طراحی زیر پیشنهاد شده است:

public class AreaCalculator
{
    public double Area(Shape[] shapes)
    {
        double area = 0;

        foreach (var shape in shapes)
        {
            area += shape.Area();
        }

        return area;
    }
}
public abstract class Shape
{
    public abstract double Area();
}
public class Square : Shape
{
    public double Height { get { return _height; } }
    private double _height;

    public Square(double Height)
    {
        _height = Height;
    }

    public override double Area()
    {
        return Math.Sqrt(_height);
    }
}
public class Circle : Shape
{
    public double Radius { get { return _radius; } }

    private double _radius;

    public Circle(double Radius)
    {
        _radius = Radius;
    }

    public override double Area()
    {
        return _radius * _radius * Math.PI;
    }
}
public class Triangle : Shape
{
    public double FirstSide { get { return _firstSide; } }
    public double SecondSide { get { return _secondSide; } }
    public double ThirdSide { get { return _thirdSide; } }

    private double _firstSide;
    private double _secondSide;
    private double _thirdSide;

    public Triangle(double FirstSide, double SecondSide, double ThirdSide)
    {
        _firstSide = FirstSide;
        _secondSide = SecondSide;
        _thirdSide = ThirdSide;
    }

    public override double Area()
    {
        double TotalHalf = (_firstSide + _secondSide + _thirdSide) / 2;
        return Math.Sqrt(TotalHalf * (TotalHalf - _firstSide) * (TotalHalf - _secondSide) * (TotalHalf - _thirdSide));
    }
}

در این طراحی، پیچیدگی محاسبه مساحت هر شکل به کلاس آن شکل منتقل شده است و با اضافه شدن شکل جدید نیازی به تغییر کلاس AreaCalculator نداریم.

در مقاله‌ی بعدی به سه اصل دیگر اصول SOLID خواهم پرداخت.

مطالب
بررسی Source Generators در #C - قسمت دوم - یک مثال
یک مثال: پیاده سازی INotifyPropertyChanged توسط Source Generators

هدف از اینترفیس INotifyPropertyChanged که به همراه یک رخ‌داد است:
public interface INotifyPropertyChanged  
{ 
   event PropertyChangedEventHandler PropertyChanged;  
}
مطلع سازی استفاده کننده‌ی از یک شیء، از تغییرات رخ‌داده‌ی در مقادیر خواص آن است که نمونه‌ی آن، در برنامه‌های WPF، جهت به روز رسانی UI، زیاد مورد استفاده قرار می‌گیرد. البته این رخ‌داد به خودی خود کار خاصی را انجام نمی‌دهد و برای استفاده‌ی از آن، باید مقدار زیادی کد نوشت و این مقدار کد نیز باید به ازای تک تک خواص یک کلاس مدل، تکرار شوند:
  partial class CarModel : INotifyPropertyChanged
  {

    private double _speedKmPerHour;
    
    public double SpeedKmPerHour
    {
      get => _speedKmPerHour;
      set
      {
        _speedKmPerHour = value;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SpeedKmPerHour)));
      }
    }

    public event PropertyChangedEventHandler? PropertyChanged;
  }
همچنین باید درنظر داشت که با تغییر نام خاصیتی، میزان قابل ملاحظه‌ای از این کدهای تکراری نیز باید به روز رسانی شوند که این عملیات می‌تواند ایده‌ی خوبی برای استفاده‌ی از Source Generators باشد.
اگر بخواهیم تولید این کدهای تکراری را به Source Generators محول کنیم، می‌توان برای مثال فیلد خصوصی مرتبط را نگه داشت و تولید مابقی کدها را خودکار کرد:
  partial class CarModel : INotifyPropertyChanged
  {
    private double _speedKmPerHour;    
  }
در این حالت کلاس مدل، به صورت partial تعریف می‌شود و فقط فیلد خصوصی، در کدهای ما حضور خواهد داشت. مابقی کدهای این کلاس partial به صورت خودکار توسط یک Source Generator سفارشی تولید خواهد شد. همانطور که ملاحظه می‌کنید، کاهش حجم قابل ملاحظه‌ای حاصل شده و همچنین اگر فیلد خصوصی دیگری نیز در اینجا اضافه شود، واکنش Source Generator ما آنی خواهد بود و بلافاصله کدهای مرتبط را تولید می‌کند و برنامه، بدون مشکلی کامپایل خواهد شد؛ هرچند به ظاهر INotifyPropertyChanged ذکر شده، در این کلاس اصلا پیاده سازی نشده‌است.


ایجاد پروژه‌ی Source Generator

در ابتدا برای ایجاد تولید کننده‌ی خودکار کدهای INotifyPropertyChanged، یک class library را به solution جاری اضافه می‌کنیم. سپس نیاز است ارجاعاتی را به دو بسته‌ی نیوگت زیر نیز افزود:
<Project Sdk="Microsoft.NET.Sdk">

  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.2.0" PrivateAssets="all" />
  </ItemGroup>
</Project>
سپس کلاس جدید NotifyPropertyChangedGenerator را به نحو زیر به آن اضافه می‌کنیم:
  [Generator]
  public class NotifyPropertyChangedGenerator : ISourceGenerator
  {
    public void Initialize(GeneratorInitializationContext context)
    {
    }

    public void Execute(GeneratorExecutionContext context)
    {
- این کلاس باید اینترفیس ISourceGenerator را پیاده سازی کرده و همچنین مزین به ویژگی Generator باشد.
- اینترفیس ISourceGenerator به همراه دو متد Initialize و Execute است که در صورت نیاز باید پیاده سازی شوند.

در متد Execute، به خاصیت context.Compilation دسترسی داریم. این خاصیت تمام اطلاعاتی را که کامپایلر از Solution جاری در اختیار دارد، به توسعه دهنده ارائه می‌دهد. برای نمونه پیاده سازی متد Execute تولید کننده‌ی کد مثال جاری، چنین شکلی را دارد:
    public void Execute(GeneratorExecutionContext context)
    {
      // uncomment to debug the actual build of the target project
      // Debugger.Launch();
      var compilation = context.Compilation;
      var notifyInterface = compilation.GetTypeByMetadataName("System.ComponentModel.INotifyPropertyChanged");

      foreach (var syntaxTree in compilation.SyntaxTrees)
      {
        var semanticModel = compilation.GetSemanticModel(syntaxTree);
        var immutableHashSet = syntaxTree.GetRoot()
          .DescendantNodesAndSelf()
          .OfType<ClassDeclarationSyntax>()
          .Select(x => semanticModel.GetDeclaredSymbol(x))
          .OfType<ITypeSymbol>()
          .Where(x => x.Interfaces.Contains(notifyInterface))
          .ToImmutableHashSet();

        foreach (var typeSymbol in immutableHashSet)
        {
          var source = GeneratePropertyChanged(typeSymbol);
          context.AddSource($"{typeSymbol.Name}.Notify.cs", source);
        }
      }
    }
در اینجا با استفاده از context.Compilation به اطلاعات کامپایلر دسترسی پیدا کرده و سپس SyntaxTrees آن‌را یکی یکی، جهت یافتن کلاس‌ها و یا همان ClassDeclarationSyntax ها، پیمایش و بررسی می‌کنیم. سپس از بین این کلاس‌ها، کلاس‌هایی که INotifyPropertyChanged را پیاده سازی کرده باشند، انتخاب می‌کنیم که اطلاعات آن در پایان کار، به متد GeneratePropertyChanged جهت تولید مابقی کدهای partial class ارسال شده و کد تولیدی، به context اضافه می‌شود تا به نحو متداولی همانند سایر کدهای برنامه، به مجموعه کدهای مورد بررسی کامپایلر اضافه شود.

نکته‌ی مهم و جالب در اینجا این است که نیازی نیست تا قطعه کد جدید را به صورت SyntaxTrees در آورد و به کامپایلر اضافه کرد. می‌توان این قطعه کد را به نحو متداولی، به صورت یک قطعه رشته‌ی استاندارد #C، تولید و به کامپایلر با متد context.AddSource ارائه کرد که نمونه‌ای از آن‌را در ذیل مشاهده می‌کنید:
    private string GeneratePropertyChanged(ITypeSymbol typeSymbol)
    {
      return $@"
using System.ComponentModel;

namespace {typeSymbol.ContainingNamespace}
{{
  partial class {typeSymbol.Name}
  {{
    {GenerateProperties(typeSymbol)}
    public event PropertyChangedEventHandler? PropertyChanged;
  }}
}}";
    }

    private static string GenerateProperties(ITypeSymbol typeSymbol)
    {
      var sb = new StringBuilder();
      var suffix = "BackingField";

      foreach (var fieldSymbol in typeSymbol.GetMembers().OfType<IFieldSymbol>()
        .Where(x=>x.Name.EndsWith(suffix)))
      {
        var propertyName = fieldSymbol.Name[..^suffix.Length];
        sb.AppendLine($@"
    public {fieldSymbol.Type} {propertyName}
    {{
      get => {fieldSymbol.Name};
      set
      {{
        {fieldSymbol.Name} = value;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof({propertyName})));
      }}
    }}");
      }

      return sb.ToString();
    }
در اینجا در ابتدا بدنه‌ی کلاس partial تکمیل می‌شود. سپس خواص عمومی آن بر اساس فیلدهای خصوصی تعریف شده، تکمیل می‌شوند. در این مثال اگر یک فیلد خصوصی به عبارت BackingField ختم شود، به عنوان فیلدی که قرار است معادل کدهای INotifyPropertyChanged را داشته باشد، شناسایی می‌شود و به همراه کدهای تولید شده‌ی خودکار خواهد بود.

کدهای source generator ما همین مقدار بیش‌تر نیست. اکنون می‌خواهیم از آن در یک برنامه‌ی کنسول جدید (برای مثال به نام NotifyPropertyChangedGenerator.Demo) استفاده کنیم. برای اینکار نیاز است ارجاعی را به آن اضافه کنیم؛ اما این ارجاع، یک ارجاع متداول نیست و نیاز به ذکر چنین ویژگی خاصی وجود دارد:
<Project Sdk="Microsoft.NET.Sdk">

  <ItemGroup>
    <ProjectReference Include="..\NotifyPropertyChangedGenerator\NotifyPropertyChangedGenerator.csproj"
                      OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
  </ItemGroup>
</Project>
در اینجا میسر دهی پروژه‌ی تولید کننده‌ی کد، همانند سایر پروژه‌ها است؛ اما نوع آن باید آنالایزر معرفی شود. به همین جهت از خاصیت OutputItemType با مقدار Analyzer استفاده شده‌است. همچنین تنظیم ReferenceOutputAssembly به false به این معنا است که این اسمبلی ویژه، یک وابستگی و dependency واقعی پروژه‌ی جاری نیست و ما قرار نیست به صورت مستقیمی از کدهای آن استفاده کنیم.

برای آزمایش این تولید کننده‌ی کد، کلاس CarModel را به صورت زیر به پروژه‌ی کنسول آزمایشی اضافه می‌کنیم:
using System.ComponentModel;

namespace NotifyPropertyChangedGenerator.Demo
{
  public partial class CarModel : INotifyPropertyChanged
  {
    private double SpeedKmPerHourBackingField;
    private int NumberOfDoorsBackingField;
    private string ModelBackingField = "";

    public void SpeedUp() => SpeedKmPerHour *= 1.1;
  }
}
این کلاس پیاده سازی کننده‌ی INotifyPropertyChanged است؛ اما به همراه هیچ خاصیت عمومی نیست. فقط به همراه یکسری فیلد خصوصی ختم شده‌ی به «BackingField» است که توسط تولید کننده‌ی کد شناسایی شده و اطلاعات آن‌ها تکمیل می‌شود. فقط باید دقت داشت که این کلاس حتما باید به صورت partial تعریف شود تا امکان تکمیل خودکار کدهای آن وجود داشته باشد.

یک نکته:   در این حالت هرچند برنامه بدون مشکل کامپایل و اجرا می‌شود، ممکن است خطوط قرمزی را در IDE خود مشاهده کنید که عنوان می‌کند این قطعه از کد قابل کامپایل نیست. اگر با چنین صحنه‌ای مواجه شدید، یکبار solution را بسته و مجددا باز کنید تا تولید کننده‌ی کد، به خوبی شناسایی شود. البته نگارش‌های جدیدتر Visual Studio و Rider به همراه قابلیت auto reload پروژه برای کار با تولید کننده‌‌های کد هستند و دیگر شاهد چنین صحنه‌هایی نیستیم و حتی اگر برای مثال فیلد جدیدی را به CarModel اضافه کنیم، نه فقط بلافاصله کدهای متناظر آن تولید می‌شوند، بلکه خواص عمومی تولید شده در Intellisense نیز قابل دسترسی هستند.


نحوه‌ی مشاهده‌ی کدهای خودکار تولید شده

اگر علاقمند باشید تا کدهای خودکار تولید شده را مشاهده کنید، در Visual Studio، در قسمت و درخت نمایشی dependencies پروژه، گره‌ای به نام Analyzers وجود دارد که در آن برای مثال نام NotifyPropertyChangedGenerator و ذیل آن، کلاس‌های تولید شده‌ی توسط آن، قابل مشاهده و دسترسی هستند و حتی قابل دیباگ نیز می‌باشند؛ یعنی می‌توان بر روی سطور مختلف آن، break-point قرار داد.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: SourceGeneratorTests.zip

معرفی تعدادی منبع تکمیلی
- برنامه Source generator playground
در اینجا تعدادی مثال را که توسط مایکروسافت توسعه یافته‌است، مشاهده می‌کنید که اتفاقا یکی از آن‌ها پیاده سازی تولید کننده‌ی کد اینترفیس INotifyPropertyChanged است. در این برنامه، خروجی کدهای تولیدی نیز به سادگی قابل مشاهده‌است.

- برنامه SharpLab
برای توسعه‌ی تولید کننده‌های کد، عموما نیاز است تا با Roslyn API آشنا بود. در این برنامه اگر از منوی بالای صفحه قسمت results، گزینه‌ی «syntax tree» را انتخاب کنید و سپس قسمتی از کد خود را انتخاب کنید، بلافاصله معادل Roslyn API آن، در سمت راست صفحه نمایش داده می‌شود.

- معرفی مجموعه‌ای از Source Generators
در اینجا می‌توان مجموعه‌ای از پروژه‌های سورس باز Source Generators را مشاهده و کدهای آن‌ها را مطالعه کنید و یا از آن‌ها در پروژه‌های خود استفاده نمائید.

- معرفی یک cookbook در مورد Source Generators
این cookbook توسط خود مایکروسافت تهیه شده‌است و جهت شروع به کار با این فناوری، بسیار مفید است.

- مجموعه مثال‌های Source generators از مایکروسافت
در اینجا می‌توانید مجموعه مثال‌هایی از Source generators را که توسط مایکروسافت تهیه شده‌است، مشاهده کنید. شرح و توضیحات تعدادی از آن‌ها را هم در اینجا مطالعه کنید.
بازخوردهای دوره
معرفی پروژه NotifyPropertyWeaver
حیفم اومد نظر ندم دستتون درد نکنه فوق العاده عالی بود.
دو تا نکته جالبی که من دیدم:
اگه بدنه set رو تعریف کرده باشید ولی OnPropertyChanged  نداشته باشه بازهم به بدنه ست OnPropertyChanged اضافه میکنه.
اگه پروپرتی‌های دیگه ای تو متد get خودشون از این پروپرتی استفاده کرده باشند برای هر کدوم از اونها هم OnPropertyChanged اضافه می‌کنه.
نظرات مطالب
PersianDatePicker یک DatePicker شمسی به زبان JavaScript که از تاریخ سرور استفاده می‌کند
برای استفاده از این برای مقدار دهی به یک پروپرتی از یک مدل تو mvc تایپ اون فیلد رو چی باید در نظر بگیرم؟
   public DateTime deliveryDate { get; set; }
الان وقتی با این دستور اسکریپت رو فراخوانی می‌کنم فقط قالب datepicker رو می‌بینم روزا رو نمی‌بینم. اگر میشه راهنماییم کنید. ممنون
 @Html.TextBoxFor(model => model.deliveryDate,
 new { @id = "TextBox1",onclick = "PersianDatePicker.Show(this,'PersianDateTime.Now.now.ToString(PersianDateTimeFormat.Date)')" })
مطالب
آشنایی با مفهوم Indexer در C#.NET
زمانی که صحبت از Indexer می‌شود، بطور ناخوداگاه ذهنمان به سمت آرایه‌ها می‌رود. آرایه‌ها در واقع ساده‌ترین اشیاء ی هستند که مفهوم Index در آنها معنا دار است.
اگر با آرایه‌ها کار کرده باشید با عملگر [] در سی شارپ آشنایی دارید. یک Indexer در واقع نوع خاصی از خاصیت (property) است که در بدنه کلاس تعریف می‌شود و به ما امکان استفاده از عملگر [] را برای نمونه کلاس فراهم می‌کند.
همانطور که به شباهت Indexer و Property اشاره شد نحوه تعریف Indexer بصورت زیر می‌باشد:
public type this [type identifier]
{
     get{ ... }
     set{ ... }
}
در پیاده سازی Indexer به نکات زیر دقت کنید:
  • از کلمه کلیدی this برای نعریف Indexer استفاده می‌شود و یک Indexer نام ندارد.
  • از دستیاب get برای برگرداندن مقدار و از دستیاب set جهت مقدار دهی و انتساب استفاده می‌شود.
  • الزامی به پیاده سازی Indexer با مقادیر صحیح عددی نیست و شما می‌توانید Indexer ی با پارامترهایی از نوع string یا double داشته باشید.
  • Indexer‌ها قابلیت سربارگذاری (overloaded) دارند.
  • Indexer‌ها می‌توانند چندین پارامتری باشند مانند آرایه‌های دو بعدی که دو پارامتری هستند.
به مثالی جهت پیاده سازی کلاس ماتریس توجه کنید:
public class Matrix
{
    // فیلدها
    private int _row, _col;
    private readonly double[,] _values;

    // تعداد ردیف‌های ماتریس
    public int Row
    {
        get { return _row; }
        set
        {
            _row = value > 0 ? value : 3;
        }
    }

    // تعداد ستون‌های ماتریس
    public int Col
    {
        get { return _col; }
        set
        {
            _col = value > 0 ? value : 3;
        }
    }

    // نعریف یک ایندکسر
    public double this[int r, int c]
    {
        get { return Math.Round(_values[r, c], 3); }
        set { _values[r, c] = value; }
    }

    // سازنده 1
    public Matrix()
    {
        _values = new double[_row,_col];
    }

    // سازنده 2
    public Matrix(int row, int col)
    {
        _row = row;
        _col = col;
        _values = new double[_row,_col];
    }

}
نحوه استفاده از کلاس ایجاد شده:
public class UseMatrixIndexer
{
    // ایجاد نمونه از شیء ماتریس
    private readonly Matrix m = new Matrix(5, 5);

    private double item;

    public UseMatrixIndexer()
    {
        // دسترسی به عنصر واقع در ردیف چهار و ستون سه
        item = m[4, 3];
    }
}
مطالب
نگاشت اشیاء در AutoMapper توسط Attribute ها #2 - تبدیل ویژگی‌ها به نگاشت
پس از معرفی ویژگی‌های لازم، در ادامه با نحوه‌ی تبدیل این ویژگی‌ها به معادل نگاشت آن‌ها در automapper خواهم پرداخت.
متد زیر هسته‌ی اصلی عملیات است و کلیه‌ی نگاشت‌های لازم را انجام می‌دهد. این متد وظیفه‌ی تبدیل نگاشت‌ها را دارد. نگاشت‌هایی که با Attributes مشخص شده‌اند:
 public static void Initialize(Assembly assembly)
 {
     //register global convertors.
     AutoMapper.Mapper.CreateMap<DateTime, string>().ConvertUsing<DateTimeToPersianDateTimeConverter>();

     var typesToMap = from t in assembly.GetTypes()
         let attr = t.GetCustomAttribute<MapFromAttribute>()
         where attr != null
         select new {SourceType = attr.SourceType, Destination = t, Attribute = attr};

     foreach (var map in typesToMap)
     {
         AutoMapper.Mapper.CreateMap(map.SourceType, map.Destination)             
             .DoMapForMemberAttribute() // for different property names in source and destination
             .DoIgnoreMapAttribute()// ignore specified properties
             .DoUseValueResolverAttribute()// set value resolvers
             .DoIgnoreAllNonExisting()// its have to be the latest.
             ;
     } //endeach
     AutoMapper.Mapper.AssertConfigurationIsValid();
 }
ورودی این متد اسملبی مربوط به ویوومدل می‌باشد (برای زمانیکه ویوومدل‌ها در اسمبلی دیگری باشند).
در سطر اول، اقدام به رجیستر کردن کلیه‌ی مبدل‌های سراسری می‌کنیم. در این سطر مبدل تاریخ به کوچی خورشیدی مورد استفاده قرار گرفته است. سپس در اسمبلی داده شده، کلیه نوع‌هایی که ویژگی MapFromAttribute را دارند، یافته و جدا می‌کنیم. در حلقه‌ی foreach ابتدا نگاشت نوع مبدأ و مقصد را انجام می‌دهیم. خروجی این متد از نوع IMappingExpression است. گر چه این اینترفیس برای تغییر بسته است، ولی قابل توسعه می‌باشد و عملیات را توسط متدهای الحاقی انجام می‌دهیم(اصل OCP).
اگر به نحوه‌ی نامگذاری متدهای الحاقی تعریف شده دقت کرده باشید، تنها کلمه‌ی Do به ابتدای نام ویژگی‌ها اضافه شده است
.

متد الحاقی DoMapFormMemberAttribute
public static IMappingExpression DoMapForMemberAttribute(this IMappingExpression expression)
{
    var ok =
        from p in expression.TypeMap.DestinationType.GetProperties()
        let attr = p.GetCustomAttribute<MapForMemberAttribute>()
        where attr != null
        select new {AttributeValue = attr, PropertyName = p.Name};

     foreach (var property in ok)
     {
         expression.ForMember(property.PropertyName, 
             opt => opt.MapFrom(property.AttributeValue.MemberToMap));
     }
    return expression;
}
هر IMappingExpression دارای امکاناتی برای نگهداری و انجام فعالیت بر روی یک نگاشت می‌باشد. در کوئری ابتدای متد، کلیه‌ی پروپرتی‌هایی را که دارای ویژگی MapForMemeberAttribute می‌باشند، یافته و جدا می‌کنیم. این پروپرتی‌ها از نظر معادل اسمی در نوع مبدأ و مقصد متفاوت هستند. سپس در حلقه، کار اتصال پروپرتی مبدأ و مقصد صورت می‌گیرد.

متد الحاقی DoIgnoreMapAttribute  
public static IMappingExpression DoIgnoreAttribute(this IMappingExpression expression)
{
    foreach (var property in
        expression.TypeMap.DestinationType.GetProperties()
        .Where(x => x.GetCustomAttribute<IgnoreMapAttribute>() != null))
    {
        expression.ForMember(property.Name, opt => opt.Ignore());
    }
    return expression;
}
این متد کلیه‌ی پروپرتی‌هایی را که دارای ویژگی IgnoreMapAttribute باشند، از گردونه‌ی نگاشت automapper خارج می‌کند. به عنوان مثال پروپرتی Password در ویوومدل مربوط به تغییر گذرواژه را نظر بگیرید. این پروپرتی نباید مقدار معادلی در شیء EF داشته باشد. از طرفی هم باید در ویوو وجودداشته باشد. با استفاده از این ویژگی هیچ نگاشتی انجام نمی‌شود و می‌توان تضمین کرد که گذرواژه به ویوومدل و ویوو راه پیدا نمی‌کند.

متد الحاقی DoUseValueResolverAttribute 
public static IMappingExpression DoUseValueResolverAttribute(this IMappingExpression expression)
{
    var ok =
        from p in expression.TypeMap.DestinationType.GetProperties()
        let attr = p.GetCustomAttribute<UseValueResolverAttribute>()
        where attr != null
        select new {AttributeValue = attr, PropertyName = p.Name};

    foreach (var property in ok)
    {
        expression.ForMember(property.PropertyName,
            opt => opt.ResolveUsing(property.AttributeValue.ValueResolver));
    }
    return expression;
}
به شیوه‌ی قبل، ابتدا نوع هایی را که دارای ویژگی UseValueResolverAttribute باشند، یافته و جدا می‌کنیم. سپس در حلقه، کار نگاشت متناظر در automapper انجام می‌گیرد. لازم به ذکر است که متد opt.ResolveUsing یک شیء با کارآیی (can do) اینترفیس IValueResolver را به عنوان آرگومان می‌گیرد.

متد الحاقی DoIgnoreAllNonExisting  
public static IMappingExpression DoIgnoreAllNonExisting(this IMappingExpression expression)
{
    var attr = expression.TypeMap.DestinationType.GetCustomAttribute<MapFromAttribute>();
    
    if (attr?.IgnoreAllNonExistingProperty == false)//instead of if(attr == null || attr.IgnoreAllNonExistingProperty == false)
        return expression;
    
    foreach (var property in expression.TypeMap.GetUnmappedPropertyNames())
    {
        expression.ForMember(property, opt => opt.Ignore());
    }
    return expression;
}
این متد برحسب پرچم تعیین شده در هنگام بکارگیری ویژگی MapFromAttribute رفتار می‌کند. به این صورت که اگر موقع تعریف، مقدار IgnoreAllNonExistingProperty را صحیح اعلام کنیم، تمام پروپرتی‌های مقصد را که معادل اسمی در مبدأ نداشته باشند و همچنین هیچگونه تنظیمی جهت مشخص سازی تکلیف نگاشت آن‌ها صورت نگرفته باشد، از گردونه‌ی نگاشت Automapper خارج می‌کند.

توضیح تکمیلی:
پس از تنظیم کلیه‌ی نگاشت‌ها در automapper جهت اطمینان از صحت تنظیمات، فراخوانی متد AutoMapper.Mapper.AssertConfigurationIsValid الزامی است. یکی از عواملی که باعث شکست این متد می‌شود، وجود پروپرتی‌هایی در نوع مقصد است، بطوریکه معادل اسمی در نوع مبدأ نداشته باشند و یا تنظیمی جهت مشخص سازی نگاشت آن انجام نشده باشد (پروپرتی که قابل نگاشت نباشد). در حقیقت این شکست بسیار مفید است. به این صورت که اگر این شکست صورت نگیرد در حین نگاشت مقادیر، باید از null یا مقدار default بدون اطلاع برنامه نویس برای مقداردهی پروپرتی استفاده کند و این یک حالت نامعلوم شیء است. اگر می‌خواهید این پروپرتی‌ها مقدار پیشفرضی بگیرند و همچنین باعث شکست عملیات هم نشوند، باید بطور صریح این موضوع را اعلام کنید. این اعلام یا باید به همین روش صورت بگیرد یا باید از ویژگی IgnorMapAttribute استفاده شود. تنها تفاوت این دو، نحوه‌ی اعمال تنظیم می‌باشد. IgnorMapAttribute باید روی تک تک پروپرتی‌های مدنظر قرار گیرد، ولی در روش اول تنها کافیست که مقدار true تنظیم گردد. به‌نظر استفاده از IgnoreMapAttribute باعث طولانی شدن کدها می‌شود؛ اما توصیه می‌شود که از همین شیوه استفاده کنید.

تا اینجا کدهای مورد نیاز نوشته شدند. در ادامه به ارائه‌ی یک مثال برای نگاشت اشیاء در Automapper توسط Attributeها می‌پردازم.
مدل ساده‌ی زیر را در نظر بگیرید:
public class Student
{
    public virtual int Id { set; get; }
    public virtual string Name { set; get; }
    public virtual string Family { set; get; }
    public virtual string Email { set; get; }
    public virtual DateTime RegisterDateTime { set; get; }
    public virtual ICollection<Book> Books { set; get; }
}
public class Book
{
    public virtual int Id { set; get; }
    public virtual string Name { set; get; }
    public virtual DateTime BorrowDateTime { set; get; }
    public virtual DateTime ExpiredDateTime { set; get; }
    public virtual decimal Price { set; get; }
    [ForeignKey("StudentIdFk")]
    public virtual Student Student { set; get; }
    public virtual int StudentIdFk { set; get; }
}
با ویوومدل متناظر ذیل:
[MapFrom(typeof (Student), ignoreAllNonExistingProperty: true, alsoCopyMetadata: true)]
public class AdminStudentViewModel
{
    // [IgnoreMap]
    public int Id { set; get; }

    [MapForMember("Name")]
    public string FirstName { set; get; }

    [MapForMember("Family")]
    public string LastName { set; get; }

 [IgnoreMap]  public string Email { set; get; } [MapForMember("RegisterDateTime")] public string RegisterDateTimePersian { set; get; } [UseValueResolver(typeof (BookCountValueResolver))] public int BookCounts { set; get; } [UseValueResolver(typeof (BookPriceValueResolver))] public decimal TotalBookPrice { set; get; } };
در تنظیم ویژگی MapFromAttribute ابتدا نوع مبدأ (Student) را مشخص کردیم و بعد صراحتاً گفتیم که از نگاشت پروپرتی‌های بلاتکلیف صرف نظر کند و همچنین پرچم انتقال Data Annotation‌های EF به ویوومدل را هم برافراشتیم. توسط MapForMember پروپرتی FirstName را به پروپرتی Name در مبدأ تنظیم کردیم و LastName را به Family. همچنین Email را بصورت صریح از نگاشت شدن منع کردیم. پروپرتی BookCounts تعداد کتاب‌ها را محاسبه می‌کند و TotalBookPrice قیمت کلیه‌ی کتاب‌ها را. برای این موارد از تأمین کننده‌ی داده (Value Resolver) استفاده کردیم. این تأمین کننده‌ها می‌توانند اینچنین پیاده سازی شوند:
public class BookCountValueResolver : ValueResolver<Student, int>
{
    protected override int ResolveCore(Student source) => source.Books.Count;
};
public class BookPriceValueResolver : ValueResolver<Student, decimal>
{
    protected override decimal ResolveCore(Student source) => source.Books.Sum(b => b.Price);
};
نحوه‌ی پیکربندی و مشاهده‌ی نتایج را در یک برنامه‌ی تحت کنسول پیاده سازی کردم. متد Main آن می‌تواند اینچنین باشد:
static void Main(string[] args)
{
    var assemblyToLoad = Assembly.GetAssembly(typeof (AdminStudentViewModel));//get assembly
    global::AttributesForAutomapper.Configuration.Initialize(assemblyToLoad);//init automaper
    IList<Student> lst;
    using (var context = new MySampleContext())
    {
        lst = context.Students.Include(x => x.Books).ToList();
    }
    foreach (var student in lst)
        {
            WriteLine( $"[{student.Id}]*\n{student.Name} {student.Family}.\nmailto:{student.Email}.\nRegistered at'{student.RegisterDateTime}'");
            foreach (var book in student.Books)
                WriteLine($"\tBook name:{book.Name}, Book price:{book.Price}");
        }
    
    var lstViewModel = AutoMapper.Mapper.Map<IList<Student>, IList<AdminStudentViewModel>>(lst);
    foreach (var adminStudentViewModel in lstViewModel)
    {
        WriteLine(
            $"[{adminStudentViewModel.Id}]*\n\t{adminStudentViewModel.FirstName} {adminStudentViewModel.LastName}.\n\t" +
            $"mailto:{adminStudentViewModel.Email}.\n\tRegistered at'{adminStudentViewModel.RegisterDateTimePersian}'\n\t" +
            $"Book Counts: {adminStudentViewModel.BookCounts} with total price of {adminStudentViewModel.TotalBookPrice}");
    }
    WriteLine("Press any key to exit...");
    ReadKey();
}
ابتدا اسمبلی مربوط به ویوومدل‌ها را مشخص می‌کنیم. سپس این اسمبلی را جهت تبدیل ویژگی‌ها به نگاشت‌های معتبر automapper به متد Initialize ارسال می‌کنیم. تنها بکار بردن همین دوسطر برای اعمال تنظیم‌ها مورد نیاز می‌باشد. بعد از اجرای موفق متد Initialize، نگاشت‌های اشیاء آماده هستند.
نمونه‌ی خروجی:
[1]*
Morteza Raeisi.
mailto:MrRaeisi@outlook.com.
Registered at'23/08/1392 19:11:43'    // I'm using Windows 10 with Persian calendar as default, On other OS or calendar settings, this value is different.
        Book name:AutoMapper Attr, Book price:1000.00
        Book name:Second Book, Book price:2500.00
        Book name:Hungry Book, Book price:2500.00
...
[1]*
Morteza Raeisi. //MapForMemebers
mailto:.  // IgnoreMap
Registered at'1392/08/23 19:11' // Convert using
Book Counts: 3 with total price of 6000.00  // Value resolvers
...
دریافت کدها + مثال
مطالب دوره‌ها
ایجاد یک کلاس جدید پویا و وهله‌ای از آن در زمان اجرا توسط Reflection.Emit
توانایی‌های Reflection.Emit صرفا به ایجاد متدهایی کاملا جدید و پویا در زمان اجرا محدود نمی‌شود. برای نمونه کلاس ذیل را درنظر بگیرید:
    public class Person
    {
        private string _name;
        public string Name
        {
            get { return _name; }
        }

        public Person(string name)
        {
            _name = name;
        }
    }
در ادامه قصد داریم معادل این کلاس را به همراه وهله‌ای از آن، به صورتی کاملا پویا در زمان اجرا ایجاد کرده (تصور کنید این کلاس در برنامه وجود خارجی نداشته و تنها جهت درک بهتر کدهای IL ادامه بحث، معرفی گردیده است) و سپس مقداری را به سازنده آن ارسال کنیم.
کدهای کامل و توضیحات این typeBuilder را در ادامه ملاحظه می‌کنید:
using System;
using System.Reflection;
using System.Reflection.Emit;

namespace FastReflectionTests
{
    class Program
    {
        static void Main(string[] args)
        {
            //اسمبلی محل قرارگیری کدهای پویای نهایی در اینجا تعیین می‌شود
            //حالت دسترسی به آن اجرایی درنظر گرفته شده، امکان تعیین حالت‌های دیگری مانند ذخیره سازی نیز وجود دارد
            var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
                                      name: new AssemblyName("Demo"), access: AssemblyBuilderAccess.Run);

            // اکنون داخل این اسمبلی یک ماژول جدید را برای قرار دادن کلاس جدید خود تعریف می‌کنیم
            var moduleBuilder = assemblyBuilder.DefineDynamicModule(name: "PersonModule");

            // کار ساخت نوع و کلاس جدید شخص عمومی از اینجا شروع می‌شود
            var typeBuilder = moduleBuilder.DefineType(name: "Person", attr: TypeAttributes.Public);

            // افزودن فیلد خصوصی نام تعریف شده در سطح کلاس شخص
            var nameField = typeBuilder.DefineField(fieldName: "_name",
                                                    type: typeof(string),
                                                    attributes: FieldAttributes.Private);

            // تعریف سازنده عمومی کلاس شخص که دارای یک آرگومان رشته‌ای است
            var ctor = typeBuilder.DefineConstructor(
                                    attributes: MethodAttributes.Public,
                                    callingConvention: CallingConventions.Standard,
                                    parameterTypes: new[] { typeof(string) });
            // تعریف بدنه سازنده کلاس شخص
            // در اینجا فیلد خصوصی تعریف شده در سطح کلاس باید مقدار دهی شود
            var ctorIL = ctor.GetILGenerator();
            // نکته‌ای در مورد سازنده‌ها
            ctorIL.Emit(OpCodes.Ldarg_0); // اندیس صفر در سازنده کلاس به وهله‌ای از کلاس جاری اشاره می‌کند
            ctorIL.Emit(OpCodes.Ldarg_1); // بارگذاری آرگومان سازنده و قرار دادن آن روی پشته
            // مقدار دهی فیلد خصوصی نام که به وهله‌ای از کلاس جاری و مقدار آرگومان دریافتی نیاز دارد
            ctorIL.Emit(OpCodes.Stfld, nameField);
            ctorIL.Emit(OpCodes.Ret); // پایان کار سازنده

            // تعریف خاصیت رشته‌ای نام در کلاس شخص
            var nameProperty = typeBuilder.DefineProperty(
                                                name: "Name",
                                                attributes: PropertyAttributes.HasDefault,
                                                returnType: typeof(string),
                                                parameterTypes: null); // خاصیت پارامتر ورودی ندارد

            var namePropertyGetMethod = typeBuilder.DefineMethod(
                                                name: "get_Name",
                                                attributes: MethodAttributes.Public |
                //متد ویژه‌ای است که توسط کامپایلر پردازش و تشخیص داده می‌شود
                                                            MethodAttributes.SpecialName |
                                                            MethodAttributes.HideBySig,
                                                returnType: typeof(string),
                                                parameterTypes: Type.EmptyTypes);
            // اتصال گت متد به خاصیت رشته‌ای نام که پیشتر تعریف شد
            nameProperty.SetGetMethod(namePropertyGetMethod);

            // بدنه گت متد در اینجا تعریف خواهد شد
            var namePropertyGetMethodIL = namePropertyGetMethod.GetILGenerator();
            namePropertyGetMethodIL.Emit(OpCodes.Ldarg_0); // بارگذاری اشاره‌گری به وهله‌ای از کلاس جاری در پشته
            namePropertyGetMethodIL.Emit(OpCodes.Ldfld, nameField); // بارگذاری فیلد نام
            namePropertyGetMethodIL.Emit(OpCodes.Ret);

            var t = typeBuilder.CreateType(); // نهایی سازی کار ایجاد نوع جدید

            // ایجاد وهله‌ای از نوع جدید که پارامتری رشته‌ای به سازنده آن ارسال می‌شود
            var instance = Activator.CreateInstance(t, "Vahid");

            // دسترسی به خاصیت نام
            var nProperty = t.GetProperty("Name");
            // و دریافت مقدار آن برای نمایش
            var result = nProperty.GetValue(instance, null);

            Console.WriteLine(result);
        }
    }
}
در اینجا ایجاد یک کلاس جدید با ایجاد یک TypeBuilder واقع در فضای نام  System.Reflection.Emit آغاز می‌شود. پیش از آن نیاز است یک اسمبلی پویا و ماژولی در آن‌را برای قرار دادن کدهای پویای این TypeBuilder ایجاد کنیم. توضیحات مرتبط با دستورات مختلف را به صورت کامنت در کدهای فوق ملاحظه می‌کنید. با استفاده از TypeBuilder و متد DefineField آن می‌توان یک فیلد در سطح کلاس ایجاد کرد و یا توسط متد DefineConstructor آن، سازنده کلاس را با امضایی ویژه تعریف نمود و سپس با دسترسی به ILGenerator آن، بدنه این سازنده را همانند متدهای پویا ایجاد کرد.
اگر به کدهای فوق دقت کرده باشید، متد get_Name به خاصیت Name انتساب داده شده است. علت را در قسمت معرفی اجمالی Reflection زمانیکه لیست متدهای کلاس Person را نمایش دادیم، ملاحظه کرده‌اید. تمام خواص Auto implemented در دات نت، هر چند ظاهر ساده‌ای دارند اما در عمل به دو متد get_Name و set_Name در کدهای IL توسط کامپایلر تبدیل می‌شوند. به همین جهت در اینجا نیاز بود تا get_Name را نیز تعریف کنیم.


چند مثال تکمیلی
Populating a PropertyGrid using Reflection.Emit
Dynamically adding RaisePropertyChanged to MVVM Light ViewModels using Reflection.Emit