اشتراک‌ها
تعدادی از ویژگی‌های پیشنهادی C# 9.0
// Before
public class Widget  
{
    private readonly int _foo;
    private readonly WidgetConfiguration _config;

    public Widget(int foo, WidgetConfiguration config)
    {
         _foo = foo;
         _config = config;
    }
}


// After
public class Widget  
{
     public Widget(int _foo, WidgetConfiguration _config)
     {
          // If you wanted one of these properties to be publicly accessible, you could define
          // and set one of those here, otherwise the arguments will be privately accessible
          // as fields.
     }
}
تعدادی از ویژگی‌های پیشنهادی C# 9.0
نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 5 - فعال سازی صفحات مخصوص توسعه دهنده‌ها
در متد public void Configure کلاس آغازین برنامه، امکان تزریق وابستگی‌های تنظیمات برنامه وجود دارد:
public class Startup
{
    private readonly IConfiguration _config;

    public Startup(IConfiguration config)
    {
        _config = config;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        var value = _config["key"];
    }

    public void Configure(IApplicationBuilder app, IConfiguration config)
    {
        var value = config["key"];
    }
}
مطالب
امکان تعریف ساده‌تر خواص Immutable در C# 9.0 با معرفی ویژگی خواص Init-Only
نگاهی به روند تکاملی نحوه‌ی تعریف خواص از C# 1.0 تا C# 9.0

در C# 1.0 برای تعریف خواص، نیاز به نوشتن مقدار زیادی کد بود:
public class Person 
{ 
    public string _firstName; 
 
    public string FirstName 
    { 
        get 
        { 
            return _firstName; 
        } 
        set 
        { 
            _firstName = value; 
        } 
    }  
}
در اینجا تعریف backing field‌ها (مانند public string _firstName) و استفاده‌ی دستی از آن‌ها الزامی بود.

در C# 2.0 از لحاظ ساده سازی این تعاریف، اتفاق خاصی رخ‌نداد. فقط امکان تعریف سطوح دسترسی مانند private بر روی getter‌ها و setter‌ها میسر شد:
public string _firstName; 
public string FirstName 
{ 
    get 
    { 
        return _firstName; 
    } 
    private set 
    { 
        _firstName = value; 
    } 
}

در C# 3.0 بود که با ارائه‌ی auto-implemented properties، نحوه‌ی تعریف خواص، بسیار ساده شد و دیگر نیازی به تعریف backing field‌ها نبود؛ چون کامپایلر به صورت خودکار آن‌ها را در پشت صحنه ایجاد می‌کرد/می‌کند:
public class Person
{
   public string FirstName { get; set; }
}

در C# 6.0، امکان حذف private setter‌ها از تعریف یک خاصیت میسر شد. یعنی مثال زیر را
public class User
{
   public string Name { get; private set; }
}
به این نحو ساده‌تر و واضح‌تر نیز می‌توان نوشت:
public class User
{
   public string Name { get; }
}
به‌علاوه در همین زمان بود که امکان مقدار دهی اولیه‌ی خواص نیز در همان سطر تعریف آن‌ها ممکن شد:
public class Foo
{
   public string FirstName { get; set; } = "Initial Value";
}
پیش از این برای مقدار دهی اولیه‌ی خواص در همان کلاسی که آن‌ها را تعریف می‌کند، می‌بایستی از طریق مقدار دهی آن‌ها در سازنده‌ی کلاس اقدام می‌شد.

همچنین در C# 6.0 با معرفی expression bodied members که بر روی خواص نیز قابل اعمال است، امکان تعریف خواص readonly محاسبه شده‌ی بر اساس مقدار سایر خواص نیز میسر شد:
public class Foo
{  
   public DateTime DateOfBirth { get; set; }
   public int Age => DateTime.Now.Year - DateOfBirth.Year;  
}

و در C# 9.0، با معرفی واژه‌ی کلیدی init، امکان تعریف ساده‌تر خواص immutable ممکن شد‌ه‌است که در مطلب جاری به آن خواهیم پرداختیم.


روش غیرقابل مقدار دهی کردن خواص، در نگارش‌ها پیش از C# 9.0

در بسیاری از موارد می‌خواهیم که خاصیتی از یک کلاس مدل، در خارج از آن قابل تغییر نباشد (مانند خواص شیء‌ای که به محتوای فایل config ثابت برنامه اشاره می‌کند). راه حل فعلی آن تا پیش از C# 9.0 به صورت زیر است:
public class User
{
   public string Name { get; private set; }
}
که در این حالت دیگر نمی‌توان مقدار خاصیت Name را در خارج از کلاس User مقدار دهی کرد:
var user = new User
{
   Name = "User 1" // Compile Error
};
وبا اینکار خطای کامپایلر زیر را دریافت می‌کنیم:
The property or indexer 'User.Name' cannot be used in this context
because the set accessor is inaccessible [CS9Features]csharp(CS0272)
در این تعریف باتوجه به وجود private set، برای مقداردهی خاصیت Name می‌توان از یکی از دو روش زیر در داخل کلاس User استفاده کرد:
- تنظیم مقدار خاصیت Name در سازنده‌ی کلاس
- و یا تنظیم این مقدار در یک متد ثالث دیگر مانند SetName
public class User
{
  public User(string name)
  {
    this.Name = name;
  }

  public void SetName(string name)
  {
    this.Name = name;
  }

  public string Name { get; private set; }
}
در هر دو حالت، از مقدار دهی مستقیم خاصیت Name توسط Object Initializer (یا همان روش متداول new User { Name = "some name"}) محروم می‌شویم. همچنین در ادامه شاید نیاز باشد که این خاصیت پس از مقدار دهی اولیه، دیگر قابل تغییر نباشد؛ یا به عبارتی immutable شود. در مثال فوق هنوز هم امکان تغییر مقدار خاصیت Name درون کلاس User، با فراخوانی‌های بعدی متد SetName، وجود دارد.


معرفی خواص Init-Only در C# 9.0

برای رفع دو مشکل یاد شده (امکان تنظیم مقدار خاصیت‌ها با همان روش متداول object initializer و همچنین غیرقابل تغییر شدن آن‌ها)، اکنون در C# 9.0 می‌توان بجای private set از واژه‌ی کلیدی init استفاده کرد:
public class User
{
   public string Name { get; init; }
}
در اینجا تنها تغییر صورت گرفته، استفاده از واژه‌ی کلیدی init، در حین تعریف خاصیت Name است. به این ترتیب به دو مزیت زیر دسترسی پیدا می‌کنیم:
الف) امکان مقدار دهی خاصیت Name، در خارج بدنه‌ی کلاس User و توسط روش متداول کار با object initializer‌ها هنوز هم وجود دارد و در این حالت الزامی به تعریف یک سازنده و یا متد خاصی درون کلاس User برای مقدار دهی آن نیست:
var user = new User
{
   Name = "User 1"
};
ب) پس از اولین بار مقدار دهی این خاصیت init-only، دیگر نمی‌توان مقدار آن‌را تغییر داد:
// Compile Time Error
// Init-only property or indexer 'User.Name' can only be assigned in an object initializer,
// or on 'this' or 'base' in an instance constructor or an 'init' accessor. [CS9Features]csharp(CS8852)
user.Name = "Test";
این نکته در مورد متدهای داخل کلاس User هم صدق می‌کند:
public class User
{
   public string Name { get; init; }

   public User(string name)
   {
     this.Name = name; // Works fine
   }

   public void SetName(string name)
   {
     this.Name = name; // Compile Time Error
   }
}
می‌توان یک خاصیت init-only را برای بار اول، در سازنده‌ی همان کلاس نیز مقدار دهی کرد؛ اما مقدار دهی ثانویه‌ی آن در سایر متدهای داخل کلاس User نیز به خطای زمان کامپایل یاد شده، ختم می‌شود و مجاز نیست.


روش تعریف immutable properties در نگارش‌های پیشین #C

با استفاده از واژه‌ی readonly در نگارش‌های قبلی #C نیز می‌توان به صورت زیر، یک خاصیت را به صورت غیرقابل تغییر یا immutable در آورد:
    public class Product
    {
        public Product(string name)
        {
            _name = name;
        }

        private readonly string _name;

        public string Name => _name;
    }
هرچند این روش کار می‌کند اما دیگر همانند init-only properties نمی‌توان از طریق object initializers خاصیت Name را مقدار دهی کرد و این مقدار دهی حتما باید از طریق سازنده‌ی کلاس باشد. همچنین ایجاد یک اصطلاحا backing filed هم برای آن، کدها را طولانی‌تر می‌کند.

یک نکته: امکان استفاده‌ی از فیلدهای readonly با خواص init-only هم وجود دارد؛ از این جهت که این نوع خواص تنها در زمان نمونه سازی اولیه‌ی شیء، اجرا و مقدار دهی می‌شوند، با مفهوم readonly، سازگاری دارند:
    public class Person
    {
        private readonly string _name;

        public string Name
        {
            get => _name;
            init => _name = value;
        }
    }
نظرات مطالب
C# 12.0 - Primary Constructors
یک نکته‌ی تکمیلی: backing fields تولید شده‌ی در حالت سازنده‌های اولیه، readonly نیستند.

اگر به کدهای #Low-Level C مطلب فوق دقت کنید، به یک چنین تعاریفی می‌رسیم:
private string <FirstName>k__BackingField;
یعنی فیلدهای خصوصی متناظر با پارامترهای سازنده‌ی تعریف شده‌ی توسط کامپایلر در C# 12.0 از نوع readonly نیستند. بنابراین باید دقت داشت که این فیلدها قابل تغییر هستند و برخلاف رویه‌ی متداول تعریف فیلدهای متناظر با پارامترهای تزریق وابستگی‌ها (مانند مثال زیر)، readonly تعریف نشده‌اند:
public class AbcController : Controller
{
   private readonly IMyService _myService;

   public AbcController(IMyService myService) {
     _myService = myService;
   }
}
یعنی در حالت استفاده‌ی از سازنده‌های اولیه‌ی C# 12 می‌توان این فیلدها را تغییر داد که باید به این مورد دقت کافی را داشت. برای نمونه اگر مانند مثال زیر این فیلد را نال کنیم و تغییر دهیم، ممکن است در سایر قسمت‌های استفاده کننده‌ی از آن مشکل درست شود:
public class MyController(IMyService _myService) : ControllerBase
{
   [HttpGet]
   public IActionResult RandomNumber()
   {
     var number = _myService.RandomNumber();

     _myService = null;

     var number2 = MyRandom();
     return Ok(number + number2);            
   }

   public int MyRandom()
   {
     return _myService.RandomNumber();
   }
}
در این مثال ابتدا از myService_ استفاده شده، سپس نال شده (و با خطایی هم مواجه نمی‌شویم چون readonly نیست) و مجددا از آن استفاده شده که به علت نال بودن، دیگر چنین چیزی میسر نیست.
مطالب
تبدیل شدن زبان C# 9.0 به یک زبان اسکریپتی با معرفی ویژگی Top Level Programs
اگر به قالب ابتدایی یک برنامه‌ی کنسول #C دقت کنیم، همواره به ساختار استاندارد زیر می‌رسیم:
using System;

namespace CS9Features
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}
در اینجا یک سری import، به همراه تعریف فضای نام، تعریف کلاس و تعریف متد Main وجود دارند ... تا بتوان یک سطر Hello World را در کنسول نمایش داد. در این حالت اگر تازه شروع به یادگیری زبان #C کرده باشید، مفاهیم زیادی را باید در جهت درک آن فرا بگیرید؛ برای مثال static چیست؟ args چیست؟ کاربرد فضای نام چیست و غیره. کاری که در C# 9.0 انجام شده، امکان حذف تمام این عوامل در جهت نمایش تک سطر Hello World است که به آن top level programs و یا top level statements گفته می‌شود.


تبدیل قالب پیش‌فرض برنامه‌های کنسول به یک Top level program

در C# 9.0 می‌توان تمام سطرهای فوق را به دو سطر زیر تقلیل داد و خلاصه کرد:
using System;

Console.WriteLine("Hello World!");
این قطعه کد بدون هیچگونه مشکلی در C# 9.0 کامپایل می‌شود و به این ترتیب زبان #C را تبدیل و یا شبیه به یک «زبان اسکریپتی» ساده می‌کند.


روش استفاده از متدهای async در Top level programs

زمانیکه نقطه‌ی آغازین برنامه را تبدیل به یک top level program کردیم، دیگر دسترسی مستقیمی را به متد Main نداریم تا آن‌را async Task دار معرفی کنیم و پس از آن بتوانیم به سادگی با متدهای async کار کنیم. برای رفع این مشکل، کامپایلر فقط کافی است یک await را در قطعه کد شما پیدا کند. خودش به صورت خودکار متد Main غیرهمزمانی را جهت اجرای کدها، تشکیل می‌دهد. به همین جهت برای کار با کدهای async در اینجا، نیاز به تنظیم خاصی نیست و قطعه کد زیر که در آن متد MyMethodAsync را اجرا می‌کند، بدون مشکل کامپایل و اجرا خواهد شد:
using System;
using System.Threading.Tasks;

await MyMethodAsync();
Console.WriteLine("Hello World!");

static async Task MyMethodAsync()
{
   await Task.Yield();
}


روش دسترسی به args در Top level programs

همانطور که در قطعه کد ابتدایی این مطلب مشخص است، متد Main به همراه پارامتر string[] args نیز هست. اما اکنون در Top level programs که فاقد متد Main هستند، چگونه می‌توان به این آرگومان‌های ارسالی توسط کاربر دسترسی یافت؟
پاسخ: پارامتر args نیز هنوز در اینجا قابل دسترسی است؛ فقط به ظاهر مخفی است:
using System;

Console.WriteLine(args[0]);


ارائه‌ی return codes به فراخون در Top level programs

بعضی از برنامه‌های کنسول در انتهای متد Main خود برای مثال return 0 و یا return 1 را دارند؛ که اولی به معنای موفقیت عملیات و دومی به معنای شکست عملیات است. در top level programs نیز می‌توان این return‌ها را در انتهای کار قید کرد:
using System;
Console.WriteLine($"Hello world!");
return 1;
که یک چنین خروجی نهایی را توسط کامپایلر تولید می‌کند:
// <Program>$
using System;
using System.Runtime.CompilerServices;

[CompilerGenerated]
internal static class <Program>$
{
   private static int <Main>$(string[] args)
   {
     Console.WriteLine("Hello world!");
     return 1;
   }
}


امکان تعریف کلاس‌ها و متدها در Top level programs

در تک فایل program.cs برنامه، در حین کار با Top level programs محدودیتی از لحاظ تعریف متدها، کلاس‌ها و غیره نیست؛ یک مثال:
using System;

var greeter = new Greeter();

var helloTeacher = greeter.Greet("teacher");
var helloStudents = SayHello("students");

Console.WriteLine(helloTeacher);
Console.WriteLine(helloStudents);

static string SayHello(string name)
{
    return "Hello, " + name;
}

public class Greeter
{
    public string Greet(string name)
    {
        return "Hello, " + name;
    }
}
همانطور که مشاهده می‌کنید، در حالت کار اسکریپتی با زبان #C، امکان استفاده‌ی از کلاس‌ها و یا متدها نیز وجود دارد؛ اما با یک شرط: این تعاریف باید پس از Top-level statements قرار گیرند. یعنی اگر متد و کلاس تعریف شده را به بالای فایل انتقال دهید، به خطای کامپایلر زیر خواهید رسید:
Top-level statements must precede namespace and type declarations. [CS9Features]csharp(CS8803)


سطوح دسترسی به کلاس‌ها و متدهای تعریف شده‌ی در Top level programs

اگر قطعه کد مثال قبل را کامپایل کنیم، نمونه‌ی دی‌کامپایل شده‌ی آن به صورت زیر است:
using System;
using System.Runtime.CompilerServices;

[CompilerGenerated]
internal static class <Program>$
{
  private static void <Main>$(string[] args)
  {
   Greeter greeter = new Greeter();
   string helloTeacher = greeter.Greet("teacher");
   string helloStudents = SayHello("students");
   Console.WriteLine(helloTeacher);
   Console.WriteLine(helloStudents);

   static string SayHello(string name)
   {
    return "Hello, " + name;
   }
  }
}
همانطور که مشاهده می‌کنید، کامپایلر نه فقط نام متدها را تغییر داده‌است، بلکه سطوح دسترسی به آن‌ها را یا private و یا internal تعریف کرده‌است. به این معنا که کلاس‌ها و متدهای تعریف شده‌ی در Top level programs در سایر کتابخانه‌ها و یا برنامه‌ها، قابل استفاده و دسترسی نیستند. البته کلاس public class Greeter به همان صورت public باقی می‌ماند و سطح دسترسی آن تغییری نمی‌کند.


نوع متدهای تعریف شده‌ی در Top level programs

مثال زیر را که یک top level program است، درنظر بگیرید:
using System;

Foo();

var x = 3;

int result = AddToX(4);
Console.WriteLine(result);

static void Foo()
{
    Console.WriteLine("Foo");
}

int AddToX(int y)
{
    return x + y;
}
متد AddToX که static نیست، امکان دسترسی به متغیر x را یافته‌است. با توجه به اینکه متد Main هم static است، چطور چنین چیزی ممکن شده‌است؟
پاسخ: متدهایی که در top level programs تعریف می‌شوند در حقیقت از نوع local functions هستند که در ابتدا در C# 7.0 معرفی شدند و سپس در C# 8.0 امکان تعریف نمونه‌های static آن‌ها نیز میسر شد.
قطعه کد فوق در اصل به صورت زیر کامپایل می‌شود که متدهای AddToX و Foo در آن داخل متد Main تشکیل شده، به صورت local function تعریف شده‌اند:
// <Program>$
using System;
using System.Runtime.CompilerServices;

[CompilerGenerated]
internal static class <Program>$
{
   private static void <Main>$(string[] args)
   {
     Foo();
     int x = 3;
     int result = AddToX(4);
     Console.WriteLine(result);

     int AddToX(int y)
     {
       return x + y;
     }

     static void Foo()
     {
       Console.WriteLine("Foo");
     }
   }
}
فقط یک local function از نوع static، دسترسی به متغیرهای تعریف شده‌ی در متد Main را ندارد.
مطالب
ذخیره سازی تنظیمات برنامه‌های ASP.NET Core در بانک اطلاعاتی به کمک Entity Framework Core
در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 7 - کار با فایل‌های config » با مقدمات کار با فایل‌های تنظیمات برنامه و تامین کننده‌های مختلف آن‌ها آشنا شدیم. در این مطلب قصد داریم یک نمونه‌ی سفارشی تامین کننده‌های تنظیمات برنامه را بر اساس دریافت و ذخیره سازی اطلاعات در بانک اطلاعاتی، تهیه کنیم.


ساختار موجودیت تنظیمات برنامه

تنظیمات برنامه با هر قالبی که تهیه شوند، دست آخر به صورت یک <Dictionary<string,string در برنامه پردازش شده و قابل دسترسی می‌شوند. بنابراین موجودیت معادل این Dictionary را به صورت زیر تعریف می‌کنیم:
namespace DbConfig.Web.DomainClasses
{
    public class ConfigurationValue
    {
        public int Id { get; set; }
        public string Key { get; set; }
        public string Value { get; set; }
    }
}


ساختار Context برنامه و مقدار دهی اولیه‌ی آن

پس از تعریف موجودیت تنظیمات برنامه، آن‌را به صورت زیر به Context برنامه معرفی می‌کنیم:
    public class MyAppContext : DbContext, IUnitOfWork
    {
        public MyAppContext(DbContextOptions options) : base(options)
        { }

        public virtual DbSet<ConfigurationValue> Configurations { set; get; }
همچنین، برای مقدار دهی مقادیر اولیه‌ی تنظیمات برنامه نیز اینبار می‌توان به کمک متد HasData، به صورت زیر عمل کرد:
        protected override void OnModelCreating(ModelBuilder builder)
        {
            // it should be placed here, otherwise it will rewrite the following settings!
            base.OnModelCreating(builder);

            // Custom application mappings
            builder.Entity<ConfigurationValue>(entity =>
            {
                entity.Property(e => e.Key).HasMaxLength(450).IsRequired();
                entity.HasIndex(e => e.Key).IsUnique();
                entity.Property(e => e.Value).IsRequired();
                entity.HasData(new ConfigurationValue
                {
                    Id = 1,
                    Key = "key-1",
                    Value = "value_from_ef_1"
                });
                entity.HasData(new ConfigurationValue
                {
                    Id = 2,
                    Key = "key-2",
                    Value = "value_from_ef_2"
                });
            });
        }

ایجاد یک IConfigurationSource سفارشی مبتنی بر بانک اطلاعاتی

انواع و اقسام تامین کننده‌های تنظیمات برنامه در پروژه‌های ASP.NET Core، در حقیقت یک پیاده سازی سفارشی از اینترفیس IConfigurationSource هستند. به همین جهت در ادامه یک نمونه‌ی مبتنی بر EF Core آن را تهیه می‌کنیم:
    public class EFConfigurationSource : IConfigurationSource
    {
        private readonly IServiceProvider _serviceProvider;

        public EFConfigurationSource(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
        }

        public IConfigurationProvider Build(IConfigurationBuilder builder)
        {
            return new EFConfigurationProvider(_serviceProvider);
        }
    }
در اینجا چون می‌خواهیم به IUnitOfWork دسترسی پیدا کنیم، IServiceProvider را به سازنده‌ی این تامین کننده تزریق کرده‌ایم. کار اصلی ساخت آن نیز در متد Build، با ارائه‌ی یک IConfigurationProvider سفارشی انجام می‌شود. اینجا است که اطلاعات را از بانک اطلاعاتی خوانده و در اختیار سیستم تنظیمات برنامه قرار می‌دهیم:
    public class EFConfigurationProvider : ConfigurationProvider
    {
        private readonly IServiceProvider _serviceProvider;

        public EFConfigurationProvider(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
            ensureDatabaseIsCreated();
        }

        public override void Load()
        {
            using (var scope = _serviceProvider.CreateScope())
            {
                var uow = scope.ServiceProvider.GetRequiredService<IUnitOfWork>();
                this.Data?.Clear();
                this.Data = uow.Set<ConfigurationValue>()
                               .AsNoTracking()
                               .ToList()
                               .ToDictionary(c => c.Key, c => c.Value);
            }
        }

        private void ensureDatabaseIsCreated()
        {
            using (var scope = _serviceProvider.CreateScope())
            {
                var uow = scope.ServiceProvider.GetRequiredService<IUnitOfWork>();
                uow.Migrate();
            }
        }
    }
در ConfigurationProvider فوق، متد Load، در آغاز برنامه فراخوانی شده و در اینجا فرصت داریم تا خاصیت this.Data آن‌را که از نوع <Dictionary<string,string است، مقدار دهی کنیم. بنابراین از serviceProvider تزریق شده‌ی در سازنده‌ی کلاس استفاده کرده و به وهله‌ای از IUnitOfWork دسترسی پیدا می‌کنیم. سپس بر این اساس تمام رکوردهای جدول متناظر با ConfigurationValue را دریافت و توسط متد ToDictionary، تبدیل به ساختار مدنظر خاصیت this.Data می‌کنیم.
در اینجا فراخوانی متد ensureDatabaseIsCreated را نیز مشاهده می‌کنید. کلاس EFConfigurationProvider در آغاز برنامه و پیش از هر عمل دیگری وهله سازی شده و سپس متد Load آن فراخوانی می‌شود. به همین جهت نیاز است یا پیشتر، بانک اطلاعاتی را توسط دستورات Migration ایجاد کرده باشید و یا متد ensureDatabaseIsCreated، اطلاعات Migration موجود را به بانک اطلاعاتی برنامه اعمال می‌کند.


معرفی EFConfigurationSource به برنامه

جهت معرفی ساده‌تر EFConfigurationSource تهیه شده، ابتدا یک متد الحاقی را بر اساس آن تهیه می‌کنیم:
    public static class EFExtensions
    {
        public static IConfigurationBuilder AddEFConfig(this IConfigurationBuilder builder,
            IServiceProvider serviceProvider)
        {
            return builder.Add(new EFConfigurationSource(serviceProvider));
        }
    }
سپس می‌توان این متد AddEFConfig را به صورت زیر به تنظیمات برنامه در کلاس Startup اضافه و معرفی کرد:
namespace DbConfig.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddScoped<IUnitOfWork, MyAppContext>();
            services.AddScoped<IConfigurationValuesService, ConfigurationValuesService>();

            var connectionString = Configuration.GetConnectionString("SqlServerConnection")
                     .Replace("|DataDirectory|", Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "app_data"));
            services.AddDbContext<MyAppContext>(options =>
                    {
                        options.UseSqlServer(
                            connectionString,
                            dbOptions =>
                                {
                                    var minutes = (int)TimeSpan.FromMinutes(3).TotalSeconds;
                                    dbOptions.CommandTimeout(minutes);
                                    dbOptions.EnableRetryOnFailure();
                                });
                    });

            var serviceProvider = services.BuildServiceProvider();
            var configuration = new ConfigurationBuilder()
                                       .AddConfiguration(Configuration) // Adds all of the existing configurations
                                       .AddEFConfig(serviceProvider)
                                       .Build();
            services.AddSingleton<IConfigurationRoot>(sp => configuration); // Replace
            services.AddSingleton<IConfiguration>(sp => configuration); // Replace
در اینجا ابتدا نیاز است یک ConfigurationBuilder جدید را ایجاد کنیم تا بتوان AddEFConfig را بر روی آن فراخوانی کرد. در این بین، خود برنامه نیز تعدادی تامین کننده‌ی تنظیمات پیش‌فرض را نیز دارد که قصد نداریم سبب پاک شدن آن‌ها شویم. به همین جهت آن‌ها را توسط متد AddConfiguration، افزوده‌ایم. پس از تعریف این ConfigurationBuilder جدید، نیاز است آن‌را جایگزین IConfiguration و IConfigurationRoot پیش‌فرض برنامه کنیم که روش آن‌را در دو متد services.AddSingleton ملاحظه می‌کنید.
همچنین روش دسترسی به serviceProvider مورد نیاز AddEFConfig، توسط متد services.BuildServiceProvider نیز در کدهای فوق مشخص است. به همین جهت مجبور شدیم این تعریف را در اینجا قرار دهیم و گرنه می‌شد از کلاس Program و یا حتی سازنده‌ی کلاس Startup نیز استفاده کرد. مشکل این دو مکان عدم دسترسی به سرویس IUnitOfWork و سایر تنظیمات برنامه است.


آزمایش برنامه

اگر به قسمت «ساختار Context برنامه و مقدار دهی اولیه‌ی آن» مطلب جاری دقت کرده باشید، دو کلید پیش‌فرض در اینجا ثبت شده‌اند. به همین جهت در ادامه با تزریق سرویس IConfiguration به سازنده‌ی یک کنترلر، سعی در خواندن مقادیر آن‌ها خواهیم کرد:
namespace DbConfig.Web.Controllers
{
    public class HomeController : Controller
    {
        private readonly IConfiguration _configuration;

        public HomeController(IConfiguration configuration)
        {
            _configuration = configuration;
        }

        public IActionResult Index()
        {
            return Json(
                new
                {
                    key1 = _configuration["key-1"],
                    key2 = _configuration["key-2"]
                });
        }
با این خروجی:



به روز رسانی بانک اطلاعاتی برنامه و بارگذاری مجدد اطلاعات IConfiguration

فرض کنید توسط سرویسی، اطلاعات جدول ConfigurationValue را تغییر داده‌اید. نکته‌ی مهم اینجا است که اینکار سبب فراخوانی مجدد متد Load کلاس EFConfigurationProvider نخواهد شد و عملا این تغییرات در سراسر برنامه توسط تزریق اینترفیس IConfiguration قابل دسترسی نخواهند بود (مگر اینکه برنامه مجددا ری‌استارت شود). نکته‌ی به روز رسانی این اطلاعات به صورت زیر است:
    public class ConfigurationValuesService : IConfigurationValuesService
    {
        private readonly IConfiguration _configuration;

        public ConfigurationValuesService(IConfiguration configuration)
        {
            _configuration = configuration;
        }

        private void reloadEFConfigurationProvider()
        {
            ((IConfigurationRoot)_configuration).Reload();
        }
در جائیکه نیاز است پس از به روز رسانی بانک اطلاعاتی، تنظیمات برنامه را نیز بارگذاری مجدد کنید، ابتدا اینترفیس IConfiguration را به سازنده‌ی آن تزریق کرده و سپس به نحو فوق، متد Reload را فراخوانی کنید. اینکار سبب می‌شود تا یکبار دیگری متد Load کلاس EFConfigurationProvider نیز فراخوانی شود که باعث بارگذاری مجدد تنظیمات برنامه خواهد شد.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: EFCoreDbConfig.zip
مطالب
امکان ساده سازی تعاریف اشیاء در C# 9.0 با Target Typing
ویژگی جدید مورد بحث در این قسمت، «Improved Target Typing» نام دارد. اما «Target Typing» چیست؟ حدس زدن نوع یک شیء بر اساس زمینه‌ای که توسط آن تعریف شده‌است، Target Typing نامیده می‌شود. نمونه‌ای از آن‌را سال‌هاست که با استفاده از واژه‌ی کلیدی var در #C استفاده می‌کنید. اما قابلیتی که در C# 9.0 اضافه شده‌است، تقریبا معکوس آن است.


Target Typing در C# 9.0

مشکلی که بعضی‌ها با واژه‌ی کلیدی var دارند، این است که اندکی خوانایی کدها را کاهش می‌دهد و در این حالت بلافاصله مشخص نیست که نوع شیء در حال استفاده چیست. در C# 9.0 برای این دسته از برنامه نویس‌ها راه حل دیگری را پیشنهاد داده‌اند: نوع ابتدایی را مشخص کنید، اما نیازی به ذکر نوع پس از واژه‌ی کلیدی new نیست و همانند var، خود کامپایلر آن‌را حدس خواهد زد! برای توضیح آن دو کلاس ساده‌ی زیر را درنظر بگیرید:
    public class Person
    {
        public string FirstName { get; set; }
    }

    public class PersonWithCtor
    {
        public PersonWithCtor(string firstName)
        {
            this.FirstName = firstName;
        }

        public string FirstName { get; set; }
    }
روش متداول استفاده‌ی از کلاس Person ساده که بدون سازنده‌است، از ابتدایی‌ترین نگارش #C به صورت زیر است:
Person person = new Person();
این روش در C# 3.0 به صورت زیر خلاصه شد:
var person = new Person();
که در این حالت کامپایلر در زمان کامپایل، واژه‌ی کلیدی var را به صورت خودکار به نمونه‌ی قبلی تبدیل کرده و عملیات کامپایل را تکمیل می‌کند. اگر با این روش تعریف متغیرها و اشیاء مشکل دارید و به نظرتان خوانایی آن کاهش یافته، می‌توانید در C# 9.0 به صورت زیر عمل کنید:
Person person = new();
در این حالت ابتدا نوع متغیر و یا شیء ذکر می‌شود. سپس در جائیکه قرار است new صورت گیرد، دیگر نیازی به تکرار آن نیست که به آن «Improved Target Typing» هم گفته می‌شود.


Target Typing و پارامترهای سازنده‌ی کلاس‌ها در C# 9.0

در مثال فوق، کلاس PersonWithCtor به همراه یک سازنده‌ی پارامتردار تعریف شده‌است. در این حالت Target Typing آن به صورت زیر خواهد بود:
Person person = new("User 1");
و یا نمونه‌ای از آن در حین تعریف مقادیر اولیه‌ی Listها است:
var personList = new List<Person>
        {
            new ("User 1"),
            new ("User 2"),
            // ...
        };
و یا حتی در حین تعریف پارامترهای یک متد نیز می‌توان از target typing استفاده کرد و تنها به ذکر new بسنده نمود:
public void Adopt(Person p)
{
    //...
}

public void CallerMethod()
{
    this.Adopt(new Person("User 1"));
    // C# 9.0
    this.Adopt(new("User 1"));
}
نمونه‌ی دیگری از این مثال را در حین مقدار دهی پارامتر دوم متد XmlReader.Create، در اینجا مشاهده می‌کنید:
XmlReader.Create(reader, new XmlReaderSettings() { IgnoreWhitespace = true });
// C# 9.0
XmlReader.Create(reader, new() { IgnoreWhitespace = true });


Target Typing و استفاده از خواص کلاس‌ها در C# 9.0

در همان مثال اول، اگر بخواهیم خاصیت FirstName را مقدار دهی کنیم و همچنین از Target Typing نیز استفاده کنیم ... روش زیر کامپایل نخواهد شد:
Person person = new
{
   FirstName = "User 2"
};
علت اینجا است که شیء‌ای که پس از علامت انتساب قرارگرفته‌است، یک anonymous object است و قابلیت انتساب به نوع Person را ندارد. در این حالت تنها کافی است ذکر () را پس از new، فراموش نکرد؛ تا قطعه کد زیر بدون مشکل کامپایل شود:
Person person = new()
{
   FirstName = "User 2"
};


امکان استفاده‌ی از Target typing با فیلدها در C# 9.0

امکان تعریف var با فیلدهای یک کلاس در زبان #C وجود ندارد. به همین جهت مجبور هستیم یک چنین تعاریف طولانی را در سطح کلاس‌ها داشته باشیم:
private ConcurrentDictionary<string, ObservableList<Cat>> _catsBefore = new ConcurrentDictionary<string, ObservableList<Cat>>();
اما با ارائه‌ی C# 9.0، می‌توان از target typing بر روی فیلدها نیز استفاده کرد و قطعه کد فوق را به صورت زیر خلاصه کرد:
private ConcurrentDictionary<string, ObservableList<Cat>> _cats = new(); // C# 9.0
این نکته در مورد مقدار دهی اولیه‌ی خواص نیز صدق می‌کند:
public ObservableCollection<Friend> Friends { get; } = new();


امکان ترکیب null-coalescing operator با target typing در C# 9.0

null-coalescing operator یا همان ?? به این معنا است که اگر متغیر سمت چپ آن نال نبود، همان مقدار درنظر گرفته شود و اگر نال بود، متغیر سمت راست آن بازگشت داده شود. در این حالت مثال زیر را در نظر بگیرید که در آن سگ و گربه از نوع پایه‌ی حیوان تعریف شده‌اند:
public interface IAnimal
{
}

public class Dog : IAnimal
{
}

public class Cat : IAnimal
{
}
در اینجا می‌خواهیم اگر برای مثال cat نال بود، حاصل عملگر ?? به متغیری از نوع IAnimal قابل انتساب باشد:
Cat cat = null;
Dog dog = new();
IAnimal animal = cat ?? dog;
یک چنین کاری در نگارش‌های پیشین #C مجاز نیست؛ اما در C# 9.0، چون target typeهای تعریف شده، قابل تبدیل به هم هستند، کامپایلر آن‌را بدون مشکل کامپایل می‌کند (البته قرار است در نگارش نهایی آن این امر محقق شود؛ هنوز نه!).


دانستنی‌هایی در مورد Target Typing

- نوشتن ()throw new مجاز است و نوع پیش‌فرض آن، System.Exception در نظر گرفته می‌شود.
- در حالت کار با tuples، نوشتن new اضافی است:
(int a, int b) t = new(1, 2); // "new" is redundant
و همچنین اگر پارامترهای آن ذکر نشوند، با مقدار پیش‌فرض آن نوع جایگزین خواهند شد:
(int a, int b) t = new(); // OK; same as (0, 0)


محدودیت‌های Target Typing در C# 9.0

- امکان نوشتن ()var dog = new وجود ندارد؛ چون نوع سمت راست این انتساب دیگر قابل حدس زدن نیست. نمونه‌ی دیگر آن anonymous type properties است؛ مانند new { Prop = new() } که در آن برای مثال نوع خاصیت Prop قابل حدس زدن نیست.
- target typing با binary operators قابل استفاده نیست.
- به عنوان ref قابل استفاده نیست.
مطالب
واژه‌های کلیدی جدید and، or و not در C# 9.0
یکی از ویژگی‌های زبان VB، شباهت بیش از اندازه‌ی آن به زبان انگلیسی است. برای مثال در این زبان با استفاده از not و and:
If Not a And b Then
  ...
Else
  ...
EndIf
می‌توان if‌های خواناتری را نسبت به #C ایجاد کرد:
if(!(a) && b)
{
...
}
else
{
}
در ادامه خواهیم دید که چگونه C# 9.0، این آرزوی دیرین را برآورده می‌کند! البته مایکروسافت در جای دیگری هم عنوان کرده‌است که زبان VB را دیگر پیگیری نمی‌کند و تغییر خاصی را در آن شاهد نخواهید بود. شاید به همین دلیل و جذب برنامه نویس‌های VB به #C، یک چنین تغییراتی رخ داده‌اند!


معرفی واژه‌ی کلیدی جدید not در C# 9.0

در ابتدا اینترفیس نمونه‌ای را به همراه دو کلاس مشتق شده‌ی از آن درنظر بگیرید:
public interface ICommand
{
}

public class Command1 : ICommand
{
}

public class Command2 : ICommand
{
}
اکنون اگر وهله‌ای از Command1 را ایجاد کرده و بخواهیم بررسی کنیم که آیا از نوع کلاس Command2 هست یا خیر، با استفاده از pattern matching و واژه‌ی کلیدی if می‌توان به صورت زیر عمل کرد:
ICommand command = new Command1();
if (!(command is Command2))
{

}
در C# 9.0، برای خواناتر کردن یک چنین بررسی‌هایی، می‌توان از pattern matching بهبود یافته‌ی آن و واژه‌ی کلیدی جدید not نیز استفاده کرد:
if (command is not Command2)
{

}


معرفی واژه‌های کلیدی جدید and و or در C# 9.0

واژه‌های کلیدی جدید and و or نیز درک و نوشتن عبارات pattern matching را بسیار ساده می‌کنند. برای نمونه قطعه کد متداول زیر را درنظر بگیرید:
if ((command is ICommand) && !(command is Command2))
{

}
اکنون در C# 9.0 با استفاده از واژه‌های کلیدی جدید and، or و not، می‌توان قطعه کد فوق را بسیار ساده کرد:
if (command is ICommand and not Command2)
{

}
نه تنها این قطعه کد ساده‌تر شده‌است، بلکه خوانایی آن افزایش یافته‌است و مانند یک سطر نوشته شده‌ی به زبان انگلیسی به نظر می‌رسد. همچنین در این حالت نیازی هم به تکرار command، در هر بار مقایسه نیست.
و یا حتی در اینجا در صورت نیاز می‌توان از واژه‌ی کلیدی جدید or نیز استفاده کرد:
if (command is Command1 or Command2)
{

}


امکان اعمال واژه‌های کلیدی جدید and، or و not به سایر نوع‌ها نیز وجود دارند

تا اینجا مثال‌هایی را که بررسی کردیم، در مورد بررسی نوع اشیاء بود. اما می‌توان این واژه‌های کلیدی جدید در C# 9.0 را به هر نوع ممکنی نیز اعمال کرد. برای نمونه، مثال ساده‌ی زیر را که در مورد بررسی اعداد است، درنظر بگیرید:
var number = new Random().Next(1, 10);
if (number > 2 && number < 8)
{

}
اکنون در C# 9.0 و با استفاده از امکانات جدید pattern matching آن می‌توان شرط متداول فوق را به صورت زیر ساده کرد:
if (number is > 2 and < 8)
{

}
در اینجا تنها یکبار نیاز به ذکر number است و از واژه‌های کلیدی is و and استفاده شده‌است.

یک مثال دیگر: متد زیر را در نظربگیرید که با استفاده از && و || متداول #C نوشته شده‌است:
public static bool IsLetterOrSeparator(char c) =>
   (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '.' || c == ',';
روش ارائه‌ی C# 9.0 ای آن به صورت زیر است:
public static bool IsLetterOrSeparator(char c) =>
   c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z') or '.' or ',';


امکان اعمال واژه‌های کلیدی جدید and، or و not به switchها نیز وجود دارد

برای نمونه قطعه کد if/else دار متداول زیر را درنظر بگیرید که قابلیت تبدیل به یک سوئیچ را نیز دارد:
 var number = new Random().Next(1, 10);
 if (number <= 0)
 {
     Console.WriteLine("Less than or equal to 0");
 }
 else if (number > 0 && number <= 10)
 {
     Console.WriteLine("More than 0 but less than or equal to 10");
 }
 else
 {
     Console.WriteLine("More than 10");
 }
اگر بخواهیم همین قطعه کد را به کمک واژه‌های کلیدی جدید C# 9.0 و pattern matching بهبود یافته‌ی آن تبدیل به یک سوئیچ کنیم، به قطعه کد زیر خواهیم رسید:
// C#9.0
 switch (number)
 {
     case <= 0:
         Console.WriteLine("Less than or equal to 0");
         break;
     case > 0 and <= 10:
         Console.WriteLine("More than 0 but less than or equal to 10");
         break;
     default:
         Console.WriteLine("More than 10");
         break;
 }
تا پیش از C# 7.0، سوئیچ‌های #C امکان بررسی باز‌ه‌ای از مقادیر را نداشتند. از آن زمان با معرفی pattern matching، چنین محدودیتی برطرف شد و اکنون می‌توان syntax قدیمی آن‌را توسط C# 9.0، بسیار خلاصه‌تر کرد. در ذیل، معادل قطعه کد فوق را بر اساس امکانات C# 7.0 مشاهده می‌کنید که خوانایی کمتری را داشته و حجم کد نویسی بیشتری را دارد:
// C#7.0
 switch (number)
 {
     case int value when value <= 0:
         Console.WriteLine("Less than or equal to 0");
         break;
     case int value when value > 0 && value <= 10:
         Console.WriteLine("More than 0 but less than or equal to 10");
         break;
     default:
         Console.WriteLine("More than 10");
         break;
 }

و یا حتی می‌توان سوئیچ C# 9.0 را توسط switch expression بهبود یافته‌ی C# 8.0 نیز به شکل زیر بازنویسی کرد:
 var message = number switch
 {
     <= 0 => "Less than or equal to 0",
     > 0 and <= 10 => "More than 0 but less than or equal to 10",
     _ => "More than 10"
 };


انواع pattern matching‌های اضافه شده‌ی به C# 9.0

در این مطلب سعی شد مفاهیم pattern matching اضافه شده‌ی به C# 9.0، ذیل عنوان واژه‌های کلیدی جدید آن بحث شوند؛ اما هر کدام دارای نام‌های خاصی هم هستند:
الف) relational patterns: امکان استفاده‌ی از <, >, <= and >= را در الگوها میسر می‌کنند. مانند نمونه‌های سوئیچی که نوشته شد.
ب) logical patterns: امکان استفاده‌ی از واژه‌های کلیدی and، or و not را در الگوها ممکن می‌کنند.
ج) not pattern: امکان استفاده‌ی از واژه‌ی کلیدی not را در عبارات if میسر می‌کند.
د) Simple type pattern: در مثال‌های زیر، پس از انطباق با یک الگو، کاری با متغیر یا شیء مرتبط نداریم. در نگارش‌های قبلی برای صرفنظر کردن از آن، ذکر _ ضروری بود؛ اما در C#9.0 می‌توان آن‌را نیز ذکر نکرد:
private static int GetDiscount(Product p) => p switch
        {
            Food => 0, // Food _ => 0 before C# 9
            Book b => 75, // Book b _ => 75 before C# 9
            _ => 25
        };
مطالب
چگونه کد قابل تست بنویسیم - قسمت دوم و پایانی
گام 3 – از بین بردن ارتباط لایه‌ها (Loose Coupling)
بجای استفاده از اشیاء واقعی، براساس interface‌ها برنامه نویسی کنید. اگر شما کد خود را با استفاده از IShoppingCartService  به عنوان یک interface بجای استفاده از شیء واقعی ShoppingCartService نوشته باشید، زمانیکه تست را مینویسید، میتوانید یک سرویس کارت خرید جعلی (mocking) که IShoppingCartService را پیاده سازی کرده جایگزین شیء اصلی نمایید. در کد زیر، توجه کنید تنها تغییر این است که متغیر عضو اکنون از نوع IShoppingCartService  است بجای ShoppingCartService .
public interface IShoppingCartService
{
    ShoppingCart GetContents();
    ShoppingCart AddItemToCart(int itemId, int quantity);
}
public class ShoppingCartService : IShoppingCartService
{
    public ShoppingCart GetContents()
    {
        throw new NotImplementedException("Get cart from Persistence Layer");
    }
    public ShoppingCart AddItemToCart(int itemId, int quantity)
    {
        throw new NotImplementedException("Add Item to cart then return updated cart");
    }
}
public class ShoppingCart
{
    public List<product> Items { get; set; }
}
public class Product
{
    public int ItemId { get; set; }
    public string ItemName { get; set; }
}
public class ShoppingCartController : Controller
{
    //Concrete object below points to actual service
    //private ShoppingCartService _shoppingCartService;
    //loosely coupled code below uses the interface rather than the 
    //concrete object
    private IShoppingCartService _shoppingCartService;
    public ShoppingCartController()
    {
        _shoppingCartService = new ShoppingCartService();
    }
    public ActionResult GetCart()
    {
        //now using the shared instance of the shoppingCartService dependency
        ShoppingCart cart = _shoppingCartService.GetContents();
        return View("Cart", cart);
    }
    public ActionResult AddItemToCart(int itemId, int quantity)
    {
        //now using the shared instance of the shoppingCartService dependency
        ShoppingCart cart = _shoppingCartService.AddItemToCart(itemId, quantity);
        return View("Cart", cart);
    }
}

گام 4 – وابستگی‌ها را تزریق کنید
اکنون ما تمام وابستگی‌ها را در یک جا مرکزیت داده‌ایم و کد ما رابطه کمی با آن وابستگی‌ها دارد. همانند گذشته، چندین راه برای پیاده سازی این گام وجود دارد. بدون استفاده از ابزارهای کمکی برای این مفهوم، ساده‌ترین راه دوباره نویسی (Overload) متد ایجاد کننده است:
//loosely coupled code below uses the interface rather 
//than the concrete object
private IShoppingCartService _shoppingCartService;
//MVC uses this constructor 
public ShoppingCartController()
{
    _shoppingCartService = new ShoppingCartService();
}
//You can use this constructor when testing to inject the    
//ShoppingCartService dependency
public ShoppingCartController(IShoppingCartService shoppingCartService)
{
    _shoppingCartService = shoppingCartService;
}

گام 5 – تست را با استفاده از یک شیء جعلی (Mocking) انجام دهید
مثالی از یک سناریوی تست ممکن در زیر آمده است. توجه کنید که یک شیء جعلی از نوع کلاس ShoppingCartService ساخته‌ایم. این شیء جعلی فرستاده می‌شود به شیء Controller و متد GetContents پیاده سازی میشود تا بجای آنکه کد اصلی را که به منبع داده مراجعه می‌کند اجرا نماید، داده‌های جعلی و شبیه سازی شده را برگرداند. بدلیل آنکه تمام کد را نوشته ایم، بسیار سریعتر از اجرای کوئری بر روی دیتابیس اجرا خواهد شد و دیگر نگرانی بابت تهیه داده تستی و یا تصحیح داده بعد از اتمام تست (بازگرداندن داده به حالت قبل از تست) نخواهم داشت. توجه داشته باشید که بدلیل مرکزیت دادن به وابستگی‌ها در گام 2 ، تنها باید یکبار آنرا تزریق نماییم و بخاطر کاری که در گام 3 انجام شد، وابستگی ما به حدی پایین آمده که میتوانیم هر شیء‌ایی  را  (جعلی و یا حقیقی) ارسال کنیم و فقط کافیست شیء مورد نظر IShoppingCartService را پیاده سازی کرده باشد.
[TestClass]
public class ShoppingCartControllerTests
{
    [TestMethod]
    public void GetCartSmokeTest()
    {
        //arrange
        ShoppingCartController controller = 
           new ShoppingCartController(new ShoppingCartServiceStub());
        // Act
        ActionResult result = controller.GetCart();
        // Assert
        Assert.IsInstanceOfType(result, typeof(ViewResult));
    }
}
/// <summary>
/// This is is a stub of the ShoppingCartService
/// </summary>
public class ShoppingCartServiceStub : IShoppingCartService
{
    public ShoppingCart GetContents()
    {
        return new ShoppingCart
        {
            Items = new List<product> {
                new Product {ItemId = 1, ItemName = "Widget"}
            }
        };
    }
    public ShoppingCart AddItemToCart(int itemId, int quantity)
    {
        throw new NotImplementedException();
    }
}

مطالب تکمیلی
از یک ابزار کنترل وابستگی (IoC/DI) استفاده کنید:
از رایج‌ترین و عمومی‌ترین ابزارهای کنترل وابستگی برای .Net می‌توان به StructureMap و CastleWindsor اشاره کرد. در کد نویسی واقعی، شما وابستگی‌های بسیاری خواهید داشت، که این وابستگی‌ها هم وابستگی هایی دارند که به سرعت از مدیریت شما خارج خواهند شد. راه حل این مشکل استفاده از یک ابزار کنترل وابستگی خواهد بود.
از یک چارچوب تجزیه (Isolation Framework) استفاده نمایید:
برای ایجاد اشیاء جعلی ممکن است کار زیادی لازم باشد و  استفاده از یک Isolation Framework میتواند زمان و میزان کد نویسی شمارا کم کند. از رایج‌ترین این ابزارها میتوان Rhino Mocks  و Moq را نام برد.
مطالب
امکان داشتن خروجی‌های Covariant در C# 9.0
در زبان #C، زمانیکه از کلاسی ارث‌بری می‌شود، امکان بازنویسی متدهای کلاس پایه، در صورت معرفی آن‌ها به صورت virtual و یا abstract، وجود دارد؛ اما در این بازنویسی‌ها، تغییر نوع خروجی این متدها مجاز نیست. این محدودیت در C# 9.0 با معرفی Covariant returns برداشته شده‌است؛ با یک شرط: نوع جدید این خروجی، باید covariant نوع اصلی متدی باشد که از کلاس پایه‌ی آن ارث‌بری شده‌است.


وضعیت خروجی متدهای بازنویسی شده تا پیش از C# 9.0

برای توضیح بهتر Covariant returns، نیاز است مثال زیر را بررسی کنیم:
public abstract class Product
{
  public string Name { get; set; }
  public abstract ProductOrder Order(int quantity);
}

public class Book : Product
{
  public string ISBN { get; set; }
  public override ProductOrder Order(int quantity) =>
new BookOrder { Quantity = quantity, Product = this };
}

public class ProductOrder
{
  public int Quantity { get; set; }
}

public class BookOrder : ProductOrder
{
  public Book Product { get; set; }
}
در اینجا یک کلاس abstract و پایه‌ی Product وجود دارد که می‌توان متد abstract سفارش دهی آن‌را در کلاس‌های مشتق شده‌ی از آن، مانند Book، بازنویسی کرد.
همانطور که مشاهده می‌کنید، در کلاس Book، تنها خروجی که برای متد Order بازنویسی شده می‌توان درنظر گرفت، همانی است که در کلاس پایه‌ی Product تعریف شده‌است و قابل تغییر نیست؛ یعنی همان ProductOrder.
همچنین در حین استفاده‌ی از این کلاس‌ها، تبدیل خروجی متد Order، به BookOrder ضروری است:
var book = new Book
{
  Name = "My book",
  ISBN = "11-1-12-22-0"
};
BookOrder orderBook = (BookOrder)book.Order(1);


امکان تغییر خروجی متدهای بازنویسی شده در C# 9.0

در C# 9.0 با مجاز اعلام شدن خروجی‌های covariant، می‌توان تغییرات زیر را به کدهای فوق اعمال کرد:
public class Book : Product
{
  public string ISBN { get; set; }
  public override BookOrder Order(int quantity) =>
      new BookOrder { Quantity = quantity, Product = this };
}
چون کلاس BookOrder از ProductOrder تعریف شده‌ی در کلاس پایه مشتق شده‌است (مفهوم covariant بودن خروجی متد)، می‌توان در C# 9.0 آن‌را به عنوان خروجی متد Order بازنویسی شده‌ی در کلاس Book، تنظیم کرد.
مزایای این ویژگی:
- داشتن یک خروجی مختص و متناسب با کلاس کتاب، مانند BookOrder؛ بجای ارائه‌ی یک خروجی بسیار عمومی ProductOrder.
- نیاز به کار با Generics را برای اینگونه اختصاصی سازی‌ها منتفی می‌کند.
- با این تغییر، دیگر نیازی به تبدیل نوع خروجی متد Order یک کتاب نیست و سطر سفارش دهی را می‌توان به صورت زیر خلاصه کرد:
BookOrder orderBook = book.Order(1);