اشتراک‌ها
0.Visual Studio 2017 15.9 منتشر شد

Summary of Notable New Features in 15.9

Top Issues Fixed in 15.9

0.Visual Studio 2017 15.9 منتشر شد
مطالب
Roslyn #2
شروع به کار با Roslyn

Roslyn از زمان ارائه‌ی نگارش Visual Studio 14 CTP3 با ویژوال استودیو یکپارچه شد. بنابراین اگر از نگارش نهایی آن یعنی Visual Studio 2015 استفاده می‌کنید، اولین پیشنیاز کار با آن را در اختیار دارید. البته نگارش پیش نمایش آن نیز برای VS 2013 ارائه شد که در این تاریخ منسوخ شده درنظر گرفته می‌شود و دیگر به روز رسانی نخواهد شد. بنابراین نیاز است حتما Visual Studio 2015 را نصب کنید.
در حین نصب Visual Studio 2015 نیز باید گزینه‌ی نصب SDK آن‌را انتخاب کرده باشید، تا امکان تولید فایل‌های VSIX مرتبط را پیدا کنید. از این جهت که قالب‌های پروژه‌ی Roslyn، امکان تولید افزونه‌های آنالیز کدها را نیز میسر می‌کنند.
علاوه بر این‌ها نیاز است Syntax Visualizer و قالب‌های پروژه‌ی مخصوص Roslyn را نیز جداگانه نصب کنید. برای این منظور به آدرس ذیل مراجعه کرده و افزونه‌ی آن‌را نصب کنید.
NET Compiler Platform SDK.

پس از نصب این افزونه، دو قابلیت جدید به Visual studio اضافه می‌شوند:
الف) در منوی view، ذیل گزینه‌ی other windows، گزینه‌ی جدیدی به نام Syntax visualizer اضافه شده‌است.
ب) ذیل گزینه‌ی extensibility پنجره‌ی new project، تعدادی قالب پروژه‌ی جدید مخصوص Roslyn نصب شده‌اند.


البته باید دقت داشت که این قالب‌های جدید برای نمایش در این لیست، حداقل نیاز به انتخاب دات نت 4.5.2 را دارند.

از Syntax visualizer جهت مشاهده‌ی ریز جزئیات ساختار دستوری کدهای جاری استفاده می‌شود:


زمانیکه گزینه‌ی View->Other windows->Syntax visualizer را اجرا کردید، اندکی صبر کنید تا بارگذاری شود و سپس اشاره‌گر ویرایشگر را به قسمتی دلخواه حرکت دهید. در این حالت می‌توان ساختار کدها را بر اساس تفسیر Roslyn مشاهده کرد. همچنین اگر در Syntax visualizer یک نود را انتخاب کنید، بلافاصله معادل آن در ادیتور ویژوال استودیو انتخاب می‌شود. از این ابزار جهت تولید آنالایزرهای مبتنی بر Roslyn زیاد استفاده می‌شود.


تغییرات خط فرمان Visual Studio 2015 نسبت به نگارش‌های پیشین آن

خط فرمان Visual Studio 2015 به همراه کامپایلر قدیمی خط فرمان C# 5 و همچنین کامپایلر جدید مبتنی بر Roslyn مخصوص C# 6 است. این کامپایلرها را در مسیرهای ذیل می‌توانید پیدا کنید:
کامپایلر جدید مبتنی بر Roslyn در مسیر C:\Program Files (x86)\MSBuild\14.0\Bin قرار دارد و کامپایلر قدیمی C# 5 در مسیر نصب دات نت فریم ورک یعنی C:\Windows\Microsoft.NET\Framework\v4.0.30319 قرار گرفته‌است.


همانطور که مشاهده می‌کنید، نگارش کامپایلر مبتنی بر Roslyn، یک است و شماره build آن، بیانگر تاریخ کامپایل آن است:
50618=2015/06/18

اکنون شاید این سؤال مطرح شود که کامپایلر پیش فرض کدام است؟ برای یافتن آن، به منوی استارت ویندوز مراجعه کرده و گزینه‌ی developer command prompt for vs 2015 را اجرا کنید. در اینجا اگر دستور csc را اجرا کنید، خروجی آن همان کامپایلر مبتنی بر Roslyn است:


در همینجا اگر سوئیچ ? را برای مشاهده‌ی راهنمای کامپایلر خط فرمان Roslyn صادر کنید، یکی از گزینه‌‌های جدید آن، سوئیچ analyzer است:
 C:\Program Files (x86)\Microsoft Visual Studio 14.0>csc /?
Microsoft (R) Visual C# Compiler version 1.0.0.50618
Copyright (C) Microsoft Corporation. All rights reserved.


Visual C# Compiler Options

 /analyzer:<file list> Run the analyzers from this assembly
(Short form: /a)
آنالایزرها اسمبلی‌ها و افزونه‌هایی هستند که قابلیت وارد شدن به pipeline کامپایلر را داشته و امکان دخالت در صدور خطاها و اخطارهای صادر شده‌ی توسط آن‌را دارند. به عبارتی این کامپایلر خط فرمان جدید، افزونه پذیر است.
همچنین این کامپایلر نسبت به نگارش قبلی آن، دارای سوئیچ و گزینه‌ی parallel نیز می‌باشد که به کمک ساختارهای داده‌ی immutable جدید دات نت مسیر شده‌است.
مطالب
PersianDateTime جایگزینی برای System.DateTime
همانطور که در توضیح پروژه PersianDateTime آمده است، کلاس PersianDateTime جایگزینی است برای System.DateTime برای استفاده در پروژه‌هایی که احتیاج به تاریخ شمسی و ساعت رسمی ایران یا سایر کشورهای فارسی‌زبان، مستقل از Time Zone سیستم و در نظر گرفتن Daylight Saving Time، دارند. این کلاس شامل اکثر متدها، پراپرتی‌ها و عملگرهای متداول  System.DateTime است.

دسترسی به تاریخ و ساعت فعلی :
PersianDateTime now = PersianDateTime.Now;

string persianDateTime = now.ToString(); // 1392/03/09 23:37:57
string persianDate = now.ToString(PersianDateTimeFormat.Date); // 1392/03/09
string persianFullDateTime = now.ToString("dddd d MMMM yyyy ساعت hh:mm:ss tt"); // پنج شنبه 9 خرداد 1392 ساعت 11:37:57 ب.ظ

TimeSpan persianTime = now.TimeOfDay; // 23:37:57.4641984


نحوه محاسبه PersianDateTime.Now بر اساس فیلد استاتیک PersianDateTime.Mode است که مقدار پیش‌فرض آن PersianDateTimeMode.UtcOffset است.
PersianDateTime.Mode را می‌توان یکی از سه مقدار زیر قرار داد :
  • System : بر اساس تاریخ و زمان سیستم (Time Zone فعلی سیستم)
  • PersianTimeZoneInfo : بر اساس Time Zone تعیین شده در فیلد استاتیک PersianDateTime.PersianTimeZoneInfo (مستقل از Time Zone سیستم)
  • UtcOffset : بر اساس اختلاف ساعت با UTC، مشخص شده در فیلد استاتیک PersianDateTime.OffsetFromUtc (مستقل از Time Zone سیستم)

مقدار پیش‌فرض PersianDateTime.PersianTimeZoneInfo برابر Iran Standard Time Zone است. توجه داشته باشید که در این حالت از DaylightSavingTime تعیین شده در Time Zone استفاده خواهد شد که مثلا برای ایران با زمان واقعی آن اختلاف دارد و باید آنرا اصلاح کرد .

مقدار پیش‌فرض PersianDateTime.OffsetFromUtc برابر 3 ساعت و نیم است. در این حالت DaylightSavingTime با توجه به مقادیر سه فیلد استاتیک DaylightSavingTimeStart ،DaylightSavingTimeEnd و DaylightSavingTime تعیین می‌شود که به صورت پیش‌فرض برابر ساعت 24 اول فروردین، ساعت 24 سی‌ام شهریور و یک ساعت است.

تمام فیلدهای استاتیک ذکر شده به صورت public تعریف شده‌اند تا برنامه‌نویسان سایر کشورهای فارسی‌زبان بتوانند به دلخواه خود آنرا تغییر دهند.

نحوه کار با مقادیر رشته‌ای تاریخ شمسی هم اینگونه است :
PersianDateTime persianDate1 = PersianDateTime.Parse("1392/03/02");
PersianDateTime persianDate2 = PersianDateTime.Parse("1392/03/02", "23:32:56");

چند سازنده هم وجود دارد برای کسانی که تاریخ را به صورت int و ساعت را int یا short (بدون ثانیه) ذخیره می‌کنند :
PersianDateTime persianDate1 = new PersianDateTime(13920310);
PersianDateTime persianDate2 = new PersianDateTime(13920310, 233256);
PersianDateTime persianDate3 = new PersianDateTime(13920310, (short)2332);

تبدیل تاریخ میلادی به شمسی :
DateTime miladiDate = new DateTime(2013, 5, 31);
PersianDateTime persianDate = new PersianDateTime(miladiDate);

تبدیل تاریخ شمسی به میلادی :
PersianDateTime persianDate = PersianDateTime.Parse("1392/03/02");
DateTime miladiDate = persianDate.ToDateTime();


علاوه بر متد ToString معمولی، دو overload دیگر از این متد برای نمایش فرمت‌های مختلف PersianDateTime وجود دارد :

public string ToString(PersianDateTimeFormat format);

public string ToString(string format);
که اولی برای فرمت‌های معمول و دومی برای هر نوع فرمت دلخواه قابل استفاده است که چند نمونه آنرا در قسمت تعیین تاریخ و ساعت فعلی دیدید.

برخی از خصوصیات کلاس PersianDateTime :
  • Year : سال
  • Month : ماه
  • Day : روز
  • TimeOfDay : زمان سپری شده از ابتدای روز
  • TimeOfWeek :زمان سپری شده از ابتدای هفته
  • TimeOfMonth : زمان سپری شده از ابتدای ماه
  • TimeOfYear : زمان سپری شده از ابتدای سال
  • DaysInYear : تعداد روز سال
  • DaysInMonth : تعداد روز ماه
  • DayOfWeek : چندمین روز هفته
  • DayOfYear : چندمین روز سال
  • DayName : نام روز
  • MonthName : نام ماه
  • Date : تاریخ بدون زمان
  • FirstDayOfWeek : اولین روز هفته
  • LastDayOfWeek : آخرین روز هفته
  • FirstDayOfMonth : اولین روز ماه
  • LastDayOfMonth : آخرین روز ماه
  • FirstDayOfYear : اولین روز سال
  • LastDayOfYear : آخرین روز سال

برخی دیگر از متدهای کلاس PersianDateTime :

public PersianDateTime Add(TimeSpan value);
public PersianDateTime AddDays(double value);
public PersianDateTime AddMonths(int months);
public PersianDateTime AddYears(int value);
public PersianDateTime AddHours(double value);
public PersianDateTime AddMinutes(double value);
public PersianDateTime AddSeconds(double value);
همچنین تمام عملگرهای +، -، >، <، =>، =<، ==، و =! قابل استفاده هستند.
مطالب
درخت‌ها و گراف‌ها قسمت سوم
همانطور که در قسمت قبلی گفتیم، در این قسمت قرار است به پیاده سازی درخت جست و جوی دو دویی مرتب شده بپردازیم. در مطلب قبلی اشاره کردیم که ما متدهای افزودن، جستجو و حذف را قرار است به درخت اضافه کنیم و برای هر یک از این متدها توضیحاتی را ارائه خواهیم کرد. به این نکته دقت داشته باشید درختی که قصد پیاده سازی آن را داریم یک درخت متوازن نیست و ممکن است در بعضی شرایط کارآیی مطلوبی نداشته باشد.
همانند مثال‌ها و پیاده سازی‌های قبلی، دو کلاس داریم که یکی برای ساختار گره است <BinaryTreeNode<T و دیگری برای ساختار درخت اصلی <BinaryTree<T.
کلاس BinaryTreeNode که در پایین نوشته شده‌است بعدا داخل کلاس BinaryTree قرار خواهد گرفت:
internal class BinaryTreeNode<T> :
    IComparable<BinaryTreeNode<T>> where T : IComparable<T>
{
    // مقدار گره
    internal T value;
 
    // شامل گره پدر
    internal BinaryTreeNode<T> parent;
 
    // شامل گره سمت چپ
    internal BinaryTreeNode<T> leftChild;
 
    // شامل گره سمت راست
    internal BinaryTreeNode<T> rightChild;
 
    /// <summary>سازنده</summary>
    /// <param name="value">مقدار گره ریشه</param>
    public BinaryTreeNode(T value)
    {
        if (value == null)
        {
            // از آن جا که نال قابل مقایسه نیست اجازه افزودن را از آن سلب می‌کنیم
            throw new ArgumentNullException(
                "Cannot insert null value!");
        }
 
        this.value = value;
        this.parent = null;
        this.leftChild = null;
        this.rightChild = null;
    }
 
    public override string ToString()
    {
        return this.value.ToString();
    }
 
    public override int GetHashCode()
    {
        return this.value.GetHashCode();
    }
 
    public override bool Equals(object obj)
    {
        BinaryTreeNode<T> other = (BinaryTreeNode<T>)obj;
        return this.CompareTo(other) == 0;
    }
 
    public int CompareTo(BinaryTreeNode<T> other)
    {
        return this.value.CompareTo(other.value);
    }
}
تکلیف کدهای اولیه که کامنت دارند روشن است و قبلا چندین بار بررسی کردیم ولی کدها و متدهای جدیدتری نیز نوشته شده‌اند که آن‌ها را بررسی می‌کنیم:
ما در مورد این درخت می‌گوییم که همه چیز آن مرتب شده است و گره‌ها به ترتیب چیده شده اند و اینکار تنها با مقایسه کردن گره‌های درخت امکان پذیر است. این مقایسه برای برنامه نویسان از طریق یک ذخیره در یک ساختمان داده خاص یا اینکه آن را به یک نوع Type قابل مقایسه ارسال کنند امکان پذیر است. در سی شارپ نوع قابل مقایسه با کلمه‌های کلیدی زیر امکان پذیر است:
T : IComparable<T>
در اینجا T می‌تواند هر نوع داده‌ای مانند Byte و int و ... باشد؛ ولی علامت : این محدودیت را اعمال می‌کند که کلاس باید از اینترفیس IComparable ارث بری کرده باشد. این اینترفیس برای پیاده‌سازی تنها شامل تعریف یک متد است به نام (CompareTo(T obj که عمل مقایسه داخل آن انجام می‌گردد و در صورت بزرگ بودن شیء جاری از آرگومان داده شده، نتیجه‌ی برگردانده شده، مقداری مثبت، در حالت برابر بودن، مقدار 0 و کوچکتر بودن مقدارمنفی خواهد بود. شکل تعریف این اینترفیس تقریبا چنین چیزی باید باشد:
public interface IComparable<T>
{
    int CompareTo(T other);
}
نوشتن عبارت بالا در جلوی کلاس، به ما این اطمینان را می‌بخشد که که نوع یا کلاسی که به آن پاس می‌شود، یک نوع قابل مقایسه است و از طرف دیگر چون می‌خواهیم گره‌هایمان نوعی قابل مقایسه باشند <IComparable<T را هم برای آن ارث بری می‌کنیم.
همچنین چند متد دیگر را نیز override کرده‌ایم که اصلی‌ترین آن‌ها GetHashCode و Equal است. موقعی که متد CompareTo مقدار 0 بر می‌گرداند مقدار برگشتی Equals هم باید True باشد.
... و یک نکته مفید برای خاطرسپاری اینکه موقعیکه دو شیء با یکدیگر برابر باشند، کد هش تولید شده آن‌ها نیز با هم برابر هستند. به عبارتی اشیاء یکسان کد هش یکسانی دارند. این رفتار سبب می‌شود که که بتوانید مشکلات زیادی را که در رابطه با مقایسه کردن پیش می‌آید، حل نمایید. 

پیاده سازی کلاس اصلی BinarySearchTree
مهمترین نکته در کلاس زیر این مورد است که ما اصرار داشتیم، T باید از اینترفیس IComparable مشتق شده باشد. بر این حسب ما می‌توانیم با نوع داده‌هایی چون int یا string کار کنیم، چون قابل مقایسه هستند ولی نمی‌توانیم با  []int یا streamreader کار کنیم چرا که قابل مقایسه نیستند.
public class BinarySearchTree<T>    where T : IComparable<T>
{
    /// کلاسی که بالا تعریف کردیم
    internal class BinaryTreeNode<T> :
        IComparable<BinaryTreeNode<T>> where T : IComparable<T>
    {
        // …
    }
 
    /// <summary>
    /// ریشه درخت
    /// </summary>
    private BinaryTreeNode<T> root;
 
    /// <summary>
    /// سازنده کلاس
    /// </summary>
    public BinarySearchTree()
    {
        this.root = null;
    }
 
//پیاده سازی متدها مربوط به افزودن و حذف و جست و جو
}
در کد بالا ما کلاس اطلاعات گره را به کلاس اضافه می‌کنیم و یه سازنده و یک سری خصوصیت رابه آن اضافه کرده ایم.در این مرحله گام به گام هر یک از سه متد افزودن ، جست و جو و حذف را بررسی می‌کنیم و جزئیات آن را توضیح می‌دهیم.

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

پیاده سازی الگوریتم بالا در کلاس اصلی:
public void Insert(T value)
{
    this.root = Insert(value, null, root);
}
 
/// <summary>
/// متدی برای افزودن عنصر به درخت
/// </summary>
/// <param name="value">مقدار جدید</param>
/// <param name="parentNode">والد گره جدید</param>
/// <param name="node">گره فعلی که همان ریشه است</param>
/// <returns>گره افزوده شده</returns>
private BinaryTreeNode<T> Insert(T value,
        BinaryTreeNode<T> parentNode, BinaryTreeNode<T> node)
{
    if (node == null)
    {
        node = new BinaryTreeNode<T>(value);
        node.parent = parentNode;
    }
    else
    {
        int compareTo = value.CompareTo(node.value);
        if (compareTo < 0)
        {
            node.leftChild =
                Insert(value, node, node.leftChild);
        }
        else if (compareTo > 0)
        {
            node.rightChild =
                Insert(value, node, node.rightChild);
        }
    }
 
    return node;
}
متد درج سه آرگومان دارد، یکی مقدار گره جدید است؛ دوم گره والد که با هر بار صدا زدن تابع بازگشتی، گره والد تغییر خواهد کرد و به گره‌های پایین‌تر خواهد رسید و سوم گره فعلی که با هر بار پاس شدن به تابع بازگشتی، گره ریشه‌ی آن زیر درخت است.
در مقاله قبلی اگر به یاد داشته باشید گفتیم که جستجو چگونه انجام می‌شود و برای نمونه به دنبال یک عنصر هم گشتیم و جستجوی یک عنصر در این درخت بسیار آسان است. ما این کد را بدون تابع بازگشتی و تنها با یک حلقه while پیاده خواهیم کرد. هر چند مشکلی با پیاده سازی آن به صورت بازگشتی وجود ندارد.
الگوریتم از ریشه بدین صورت آغاز می‌گردد و به ترتیب انجام می‌شود:
  • اگر عنصر جدید برابر با گره فعلی باشد، همان گره را بازگشت بده.
  • اگر عنصر جدید کوچکتر از گره فعلی است، گره سمت چپ را بردار و عملیات را از ابتدا آغاز کن (در کد زیر به ابتدای حلقه برو).
  • اگر عنصر جدید بزرگتر از گره فعلی است، گره سمت راست را بردار و عملیات را از ابتدا آغاز  کن.
در انتها اگر الگوریتم، گره را پیدا کند، گره پیدا شده را باز می‌گرداند؛ ولی اگر گره را پیدا نکند، یا درخت خالی باشد، مقدار برگشتی نال خواهد بود.

حذف یک عنصر
حذف کردن در این درخت نسبت به درخت دودودیی معمولی پیچیده‌تر است. اولین گام این عمل، جستجوی گره مدنظر است. وقتی گره‌ایی را مدنظر داشته باشیم، سه بررسی زیر انجام می‌گیرد:
  • اگر گره برگ هست و والد هیچ گره‌ای نیست، به راحتی گره مد نظر را حذف می‌کنیم و ارتباط گره والد با این گره را نال می‌کنیم.
  • اگر گره تنها یک فرزند دارد (هیچ فرقی نمی‌کند چپ یا راست) گره مدنظر حذف و فرزندش را جایگزینش می‌کنیم.
  • اگر گره دو فرزند دارد، کوچکترین گره در زیر درخت سمت راست را پیدا کرده و با گره مدنظر جابجا می‌کنیم. سپس یکی از دو عملیات بالا را روی گره انجام می‌دهیم.
اجازه دهید عملیات بالا را به طور عملی بررسی کنیم. در درخت زیر ما می‌خواهیم گره 11 را حذف کنیم. پس کوچکترین گره سمت راست، یعنی 13 را پیدا می‌کنیم و با گره 11 جابجا می‌کنیم.

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

/// عنصر مورد نظر را جست و جوی می‌کند و اگر مخالف نال بود گره برگشتی را به تابع حذف ارسال می‌کند
public void Remove(T value)
{
    BinaryTreeNode<T> nodeToDelete = Find(value);
    if (nodeToDelete != null)
    {
        Remove(nodeToDelete);
    }
}
 
private void Remove(BinaryTreeNode<T> node)
{
    //بررسی می‌کند که آیا دو فرزند دارد یا خیر
    // این خط باید اول همه باشد که مرحله یک و دو بعد از آن اجرا شود
    if (node.leftChild != null && node.rightChild != null)
    {
        BinaryTreeNode<T> replacement = node.rightChild;
        while (replacement.leftChild != null)
        {
            replacement = replacement.leftChild;
        }
        node.value = replacement.value;
        node = replacement;
    }
 
    // مرحله یک و دو اینجا بررسی میشه
    BinaryTreeNode<T> theChild = node.leftChild != null ?
            node.leftChild : node.rightChild;
 
    // اگر حداقل یک فرزند داشته باشد
    if (theChild != null)
    {
        theChild.parent = node.parent;
 
        // بررسی می‌کند گره ریشه است یا خیر
        if (node.parent == null)
        {
            root = theChild;
        }
        else
        {
            // جایگزینی عنصر با زیر درخت فرزندش
            if (node.parent.leftChild == node)
            {
                node.parent.leftChild = theChild;
            }
            else
            {
                node.parent.rightChild = theChild;
            }
        }
    }
    else
    {
        // کنترل وضعیت موقعی که عنصر ریشه است
        if (node.parent == null)
        {
            root = null;
        }
        else
        {
            // اگر گره برگ است آن را حذف کن
            if (node.parent.leftChild == node)
            {
                node.parent.leftChild = null;
            }
            else
            {
                node.parent.rightChild = null;
            }
        }
    }
}

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


دو فرزندی

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


تک فرزندی

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

اگر نتیجه نال نباشد باید این گره حذف و گره فرزند ارتباطش را با والد گره حذفی برقرار کند. در صورتیکه گره حذفی ریشه باشد و والدی نداشته باشد، این نکته باید رعایت شود که گره فرزند بری متغیر root که در سطح کلاس تعریف شده است، نیز قابل شناسایی باشد.

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


بدون فرزند (برگ)

حال اگر گره ما برگ باشد مرحله دوم، کد داخل else اجرا خواهد شد و بررسی می‌کند این گره در والد فرزند چپ است یا راست و به این ترتیب با نال کردن آن فرزند در والد ارتباط قطع شده و گره از درخت حذف می‌شود.


پیمایش درخت به روش DFS یا LVR یا In-Order

public void PrintTreeDFS()
{
    PrintTreeDFS(this.root);
    Console.WriteLine();
}
 

private void PrintTreeDFS(BinaryTreeNode<T> node)
{
    if (node != null)
    {
        PrintTreeDFS(node.leftChild);
        Console.Write(node.value + " ");
        PrintTreeDFS(node.rightChild);
    }
}


در مقاله بعدی درخت دودویی متوازن را که پیچیده‌تر از این درخت است و از کارآیی بهتری برخوردار هست، بررسی می‌کنیم.

مطالب
ایجاد یک HtmlHelper سفارشی با پشتیبانی از UnobtrusiveValidationAttributes
همانطور که می‌دانید، در MVC برای اعتبارسنجی داده‌ها در سمت کلاینت از کتابخانه‌ی jquery استفاده می‌شود. مایکروسافت از طریق jquery.validate.unobtrusive و گسترش کتابخانه‌ی jquery.validate توانسته منطق خود را برای اعتبارسنجی داده‌ها در سمت کلاینت پیاده سازی کند. 
برای این منظور MVC به کنترلهایی که باید اعتبارسنجی شوند، خصوصیاتی را از طریق Data Attribute اضافه می‌کند. برای مثال اگر در مدل خود فیلد ایمیل را به شکل زیر امضاء کرده باشید:
[Display(Name = "رایانامه")]
[Required(AllowEmptyStrings = false, ErrorMessage = "رایانامه خود را وارد کنید.")]
[RegularExpression("\\w+([-+.']\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*", ErrorMessage = "نشانی رایانامه پذیرفتنی نمی‌باشد.")]
[ExistField(Action = "EmailExist", Namespace = "Parsnet.Controllers", Controller = "Account", ErrorMessage = "این رایانامه پیشتر به کار گرفته شده است.")]
        public string Email { get; set; }
و در View مورد نظر از Htmlhlper مربوطه به شکل زیر استفاده کرده باشید:
@Html.TextBoxFor(m => m.Email, new { @class = "form-control en", placeholder = @Html.DisplayNameFor(m => m.Email) })
در نهایت، Html خروجی در سمت کلاینت به شکل زیر خواهد بود:
<input data-val="true" data-val-existfiledvalidator="این رایانامه پیشتر به کار گرفته شده است." data-val-existfiledvalidator-url="/account/emailexist" data-val-regex="نشانی رایانامه پذیرفتنی نمی‌باشد." data-val-regex-pattern="\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*" data-val-required="رایانامه خود را وارد کنید." id="Email" name="Email" placeholder="رایانامه" value="" type="text">
و در اینجا کتابخانه‌ی اعتبارسنجی MVC با استفاده از همین خصوصیات *-data، اطلاعات مورد نیاز را جهت نمایش، اعتبارسنجی، تنظیم و بکارگیری، مورد استفاده قرار می‌دهد.
در یکی از پروژه‌هایی که در حال کار کردن بر روی آن هستم لازم شد تا این اطلاعات اعتبارسنجی به یک تگ span اعمال شوند. سناریوی مورد نظر به این صورت است که در بخش پروفایل کاربر، کاربر می‌تواند اطلاعات خود را بصورت inline ویرایش کنید. برای اینکار از کتابخانه X-editable استفاده کردم که از این لینک قابل دریافت است.
ابتدا اطلاعات موردنیاز در یک تگ span نمایش داده می‌شوند و در ادامه کاربر پس از کلیک بر روی آیکن ویرایش، امکان تغییر آن فیلد را دارد. برای اعتبارسنجی داده‌ها لازم بود تا تمامی اطلاعات مورد نیاز اعتبارسنجی در سمت کلاینت را به شکلی در اختیار داشته باشم و به ذهنم رسید تا با ایجاد یک Helper سفارشی، خصوصیات موردنظر را به تگ span اعمال کنم و سپس در سمت کلاینت از آن استفاده کنم. در واقع با اینکار با استفاده از همان کلاس مدل و این Helper سفارشی، از وارد کردن دستی داده‌ها و خصوصیات اجتناب کنم. (تصور کنید چیزی حدود 30 فیلد که هرکدام حداقل 4 خصوصیت دارند)
با نگاهی به سورس MVC دیدم پیاده سازی این قابلیت چندان سخت نیست و به راحتی با ایجاد یک Helper سفارشی، منطق خود را پیاده سازی و اعتبارسنجی در سمت کلاینت را به راحتی اعمال کردم.
برای ایجاد این Helper سفارشی ابتدا یک کلاس استاتیک ایجاد کنید و با استفاده از extension Method‌ها یک helper جدید را ایجاد کنید:
namespace Parsnet
{
     public static MvcHtmlString SpanFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
        {
            
            var sb = new StringBuilder();

            var span = new TagBuilder("span");

            var metadata = ModelMetadata.FromLambdaExpression<TModel, TProperty>(expression, htmlHelper.ViewData);
            var name = ExpressionHelper.GetExpressionText(expression);
            var fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
            var value = "";

            if (metadata.Model != null && metadata.Model.GetType() == typeof(List<IdentityProvider.IdentityRole>))
            {
                var modelList = (List<IdentityProvider.IdentityRole>)metadata.Model;
                value = String.Join("، ", modelList.Select(r => r.Name));
            }
            else
            {
                value = htmlHelper.FormatValue(metadata.Model, null);
            }

            span.MergeAttributes<string, object>(((IDictionary<string, object>)HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes)));

            var fieldName = fullName.Split('.')[1];
            span.MergeAttribute("data-name", fieldName, true);
            span.MergeAttributes<string, object>(htmlHelper.GetUnobtrusiveValidationAttributes(name, metadata));

            sb.Append(span.ToString(TagRenderMode.StartTag));
            sb.Append(value);
            sb.Append(span.ToString(TagRenderMode.EndTag));

            return new MvcHtmlString(sb.ToString());
        }
    }
}
ما در این helper سفارشی از عبارت‌های لامبدا استفاده می‌کنیم و با استفاده از این عبارات، فیلد مورد نظر مدل خود را به helper معرفی می‌کنیم. آرگومان htmlAttributes در متد helper نیز برای دریافت خصوصیات اضافی helper است؛ خصوصیاتی مانند class، id, style و غیره.
با استفاده از کلاس TagBuilder تگ مورد نظر خود را ایجاد می‌کنیم. در اینجا من تگ span را ایجاد کرده‌ام که شما می‌توانید هر تگ دلخواه دیگری را نیز ایجاد کنید. اولین مرحله، استخراج اطلاعات موردنیاز از metadata مدل است که در خط زیر با پردازش عبارت لامبدا اینکار صورت می‌گیرد:
var metadata = ModelMetadata.FromLambdaExpression<TModel, TProperty>(expression, htmlHelper.ViewData);
سپس نام فیلد مورد نظر را از مدل استخراج می‌کنیم:
var name = ExpressionHelper.GetExpressionText(expression);
var fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
کدهای فوق نام فیلد جاری (در اینجا Email) را از MetaData برای ما استخراج می‌کند. متغیر value برای نگهداری مقدار این فیلد از مدل است. مرحله بعد استخراج مقدار فیلد و انتساب آن به متغیر value است.
در سناریوی من کاربر می‌تواند زمینه‌ی فعالیت خود را انتخاب کند که به صورت IdentityRole پیاده سازی شده است. من در اینجا چک می‌کنیم که اگر نوع داده‌ای این فیلد List<IdentityProvider.IdentityRole> بود زمینه فعالیت کاربر را از طریق "،" از هم جدا کرده و به صورت یک رشته تبدیل می‌کنم. در غیر اینصورت همان مقدار عادی فیلد را بکار می‌گیرم.
if (metadata.Model != null && metadata.Model.GetType() == typeof(List<IdentityProvider.IdentityRole>))
            {
                var modelList = (List<IdentityProvider.IdentityRole>)metadata.Model;
                value = String.Join("، ", modelList.Select(r => r.Name));
            }
            else
            {
                value = htmlHelper.FormatValue(metadata.Model, null);
            }
سپس خصوصیات سفارشی خود را که بصورت attribute‌های HTML هستند، در خط زیر به تگ سفارشی اعمال می‌شوند:
span.MergeAttributes<string, object>(((IDictionary<string, object>)HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes)));
مهمترین مرحله که در واقع هدف اصلی من بود، استخراج خصوصیت‌های *-data برای اعتبارسجی است که در خط زیر اینکار صورت گرفته است:
 span.MergeAttributes<string, object>(htmlHelper.GetUnobtrusiveValidationAttributes(name, metadata));
نحوه‌ی استفاده از این helper سفارشی هم خیلی ساده است:
@Html.SpanFor(m => m.Profile.Email, new { @class = "editor", data_type = "text" })
و در نهایت HTML خروجی به شکل زیر است:
<span class="editor" data-name="Email" data-type="text" data-val="true" data-val-existfiledvalidator="این رایانامه پیشتر به کار گرفته شده است." data-val-existfiledvalidator-url="/account/emailexist" data-val-regex="نشانی رایانامه پذیرفتنی نمی‌باشد." data-val-regex-pattern="\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*" data-val-required="رایانامه خود را وارد کنید.">alireza_s_84@yahoo.com</span>
دیدن شکل‌های زیر خالی از لطف نیستند:

و پس از ویرایش:


البته برای درک بهتر این موضوع سعی خواهم کرد تا با یک مثال عملی کامل، نحوه‌ی پیاده سازی را در همینجا قرار دهم.
مطالب
انجام پی در پی اعمال Async به کمک Iterators - قسمت دوم

در قسمت قبل ایده‌ی اصلی و مفاهیم مرتبط با استفاده از Iterators مطرح شد. در این قسمت به یک مثال عملی در این مورد خواهیم پرداخت.

چندین کتابخانه و کلاس جهت مدیریت Coroutines در دات نت تهیه شده که لیست آن‌ها به شرح زیر است:
1) Using C# 2.0 iterators to simplify writing asynchronous code
2) Wintellect's Jeffrey Richter's PowerThreading Library
3) Rob Eisenberg's Build your own MVVM Framework codes

و ...

مورد سوم که توسط خالق اصلی کتابخانه‌ی Caliburn (یکی از فریم ورک‌های مشهور MVVM برای WPF و Silverlight) در کنفرانس MIX 2010 ارائه شد، این روزها در وبلاگ‌های مرتبط بیشتر مورد توجه قرار گرفته و تقریبا به یک روش استاندارد تبدیل شده است. این روش از یک اینترفیس و یک کلاس به شرح زیر تشکیل می‌شود:

using System;

namespace SLAsyncTest.Helper
{
public interface IResult
{
void Execute();
event EventHandler Completed;
}
}

using System;
using System.Collections.Generic;

namespace SLAsyncTest.Helper
{
public class ResultEnumerator
{
private readonly IEnumerator<IResult> _enumerator;

public ResultEnumerator(IEnumerable<IResult> children)
{
_enumerator = children.GetEnumerator();
}

public void Enumerate()
{
childCompleted(null, EventArgs.Empty);
}

private void childCompleted(object sender, EventArgs args)
{
var previous = sender as IResult;

if (previous != null)
previous.Completed -= childCompleted;

if (!_enumerator.MoveNext())
return;

var next = _enumerator.Current;
next.Completed += childCompleted;
next.Execute();
}
}
}

توضیحات:
مطابق توضیحات قسمت قبل، برای مدیریت اعمال همزمان به شکلی پی در پی، نیاز است تا یک IEnumerable را به همراه yield return در پایان هر مرحله از کار ایجاد کنیم. در اینجا این IEnumerable را از نوع اینترفیس IResult تعریف خواهیم کرد. متد Execute آن شامل کدهای عملیات Async خواهند شد و پس از پایان کار رخداد Completed صدا زده می‌شود. به این صورت کلاس ResultEnumerator به سادگی می‌تواند یکی پس از دیگری اعمال Async مورد نظر ما را به صورت متوالی فراخوانی نمائید. با هر بار فراخوانی رخداد Completed، متد MoveNext صدا زده شده و یک مرحله به جلو خواهیم رفت.
برای مثال کدهای ساده WCF Service زیر را در نظر بگیرید.

using System.ServiceModel;
using System.ServiceModel.Activation;
using System.Threading;

namespace SLAsyncTest.Web
{
[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode
= AspNetCompatibilityRequirementsMode.Allowed)]
public class TestService
{
[OperationContract]
public int GetNumber(int number)
{
Thread.Sleep(2000);//Simulating a log running operation
return number * 2;
}
}
}

قصد داریم در طی دو مرحله متوالی این WCF Service را در یک برنامه‌ی Silverlight فراخوانی کنیم. کدهای قسمت فراخوانی این سرویس بر اساس پیاده سازی اینترفیس IResult به صورت زیر درخواهند آمد:

using System;
using SLAsyncTest.Helper;

namespace SLAsyncTest.Model
{
public class GetNumber : IResult
{
public int Result { set; get; }
public bool HasError { set; get; }

private int _num;
public GetNumber(int num)
{
_num = num;
}

#region IResult Members
public void Execute()
{
var srv = new TestServiceReference.TestServiceClient();
srv.GetNumberCompleted += (sender, e) =>
{
if (e.Error == null)
Result = e.Result;
else
HasError = true;

Completed(this, EventArgs.Empty); //run the next IResult
};
srv.GetNumberAsync(_num);
}

public event EventHandler Completed;
#endregion
}
}
در متد Execute کار فراخوانی غیرهمزمان WCF Service به صورتی متداول انجام شده و در پایان متد Completed صدا زده می‌شود. همانطور که توضیح داده شد، این فراخوانی در کلاس ResultEnumerator یاد شده مورد استفاده قرار می‌گیرد.
اکنون قسمت‌های اصلی کدهای View Model برنامه به شکل زیر خواهند بود:

private void doFetch(object obj)
{
new ResultEnumerator(executeAsyncOps()).Enumerate();
}

private IEnumerable<IResult> executeAsyncOps()
{
FinalResult = 0;
IsBusy = true; //Show BusyIndicator

//Sequential Async Operations
var asyncOp1 = new GetNumber(10);
yield return asyncOp1;

//using the result of the previous step
if(asyncOp1.HasError)
{
IsBusy = false; //Hide BusyIndicator
yield break;
}

var asyncOp2 = new GetNumber(asyncOp1.Result);
yield return asyncOp2;

FinalResult = asyncOp2.Result; //Bind it to the UI

IsBusy = false; //Hide BusyIndicator
}
در اینجا یک IEnumerable از نوع IResult تعریف شده است و در طی دو مرحله‌ی متوالی اما غیرهمزمان کار دریافت اطلاعات از WCF Service صورت می‌گیرد. ابتدا عدد 10 به WCF Service ارسال می‌شود و خروجی 20 خواهد بود. سپس این عدد در مرحله‌ی بعد مجددا به WCF Service ارسال گردیده و حاصل نهایی که عدد 40 می‌باشد در اختیار سیستم Binding قرار خواهد گرفت.
اگر از این روش استفاده نمی‌شد ممکن بود به این جواب برسیم یا خیر. ممکن بود مرحله‌ی دوم ابتدا شروع شود و سپس مرحله‌ی اول رخ دهد. اما با کمک Iterators و yield return به همراه کلاس ResultEnumerator موفق شدیم تا عملیات دوم همزمان را در حالت تعلیق قرار داده و پس از پایان اولین عملیات غیر همزمان، مرحله‌ی بعدی فراخوانی را بر اساس مقدار حاصل شده از WCF Service آغاز کنیم.
این روش برای برنامه‌ نویس‌ها آشناتر است و همان سیستم فراخوانی A->B->C را تداعی می‌کند اما کلیه اعمال غیرهمزمان هستند و ترد اصلی برنامه قفل نخواهد شد.

کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید.

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

ساختار درختی
در بسیاری از مواقع ما با گروهی از اشیاء یا داده‌هایی سر و کار داریم که هر کدام از آن‌ها به گروهی دیگر مرتبط هستند. در این حالت از ساختار خطی نمی‌توانیم برای توصیف این ارتباط استفاده کنیم. پس بهترین ساختار برای نشان دادن این ارتباط ساختار شاخه ای Branched Structure است.
یک ساختار درختی یا یک ساختار شاخه‌ای شامل المان‌هایی به اسم گره Node است. هر گره می‌تواند به یک یا چند گره دیگر متصل باشد و گاهی اوقات این اتصالات مشابه یک سلسه مراتب hierarchically می‌شوند.
درخت‌ها در برنامه نویسی جایگاه ویژه‌ای دارند به طوری که استفاده‌ی از آن‌ها در بسیاری از برنامه‌ها وجود دارد و بسیاری از مثال‌های واقعی پیرامون ما را پشتیبانی می‌کنند.
در نمودار زیر مثالی وجود دارد که در آن یک تیم نرم افزاری نمایش داده شده‌است. در اینجا هر یک از بخش‌ها وظایف و مسئولیت‌هایی را بر دوش خود دارند که این مسئولیت‌ها به صورت سلسله مراتبی در تصویر زیر نمایش داده شده‌اند.

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

اصطلاحات درخت
برای اینکه بیشتر متوجه روابط بین اشیا در این ساختار بشویم، به شکل زیر خوب دقت کنید:

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

در شکل بالا به هر یک از دایره‌ها یک گره Node می‌گویند و به هر خط ارتباط دهنده بین گره‌ها لبه Edge گفته می‌شود. گره‌های 19 و 21 و 14 زیر گره‌های گره 7 محسوب می‌شوند. گره‌هایی که به صورت مستقیم به زیر گره‌های خودشان اشاره می‌کنند را گره‌های والد Parent می‌گویند و زیرگره‌های 7 را گره‌های فرزند ChildNodes. پس با این حساب می‌توانیم بگوییم گره‌های 1 و 12 و 31 را هم فرزند گره 19 هستند و گره 19 والد آن هاست. همچنین گره‌های یک والد را مثل 19 و 21 و 14 که والد مشترک دارند، گره‌های خواهر و برادر یا حتی همنژاد Sibling می‌گوییم. همچنین ارتباط بین گره 7 و گره‌های سطح دوم  و الی آخر یعنی 1 و 12 و 31 و 23 و 6 را که والد بودن آن به صورت غیر مستقیم است را جد یا ancestor می‌نامیم و نوه‌ها و نتیجه‌های آن‌ها را نسل descendants.

ریشه Root: به گره‌ای می‌گوییم که هیچ والدی ندارد و خودش در واقع اولین والد محسوب می‌شود؛ مثل گره 7.

برگ  Leaf: به گره‌هایی که هیچ فرزندی ندارند، برگ می‌گوییم. مثال گره‌های 1 و12 و 31 و 23 و 6

گره‌های داخلی Internal Nodes: گره هایی که نه برگ هستند و نه ریشه. یعنی حداقل یک فرزند دارند و خودشان یک گره فرزند محسوب می‌شوند؛ مثل گره‌های 19 و 14.

مسیر Path: راه رسیدن از یک گره به گره دیگر را مسیر می‌گویند. مثلا گره‌های 1 و 19 و 7 و 21 به ترتیب یک مسیر را تشکیل می‌دهند ولی گره‌های 1 و 19 و 23 از آن جا که هیچ جور اتصالی بین آن‌ها نیست، مسیری را تشکیل نمی‌دهند.

طول مسیر Length of Path: به تعداد لبه‌های یک مسیر، طول مسیر می‌گویند که می‌توان از تعداد گره‌ها -1 نیز آن را به دست آورد. برای نمونه : مسیر 1 و19 و 7 و 21 طول مسیرشان 3 هست.

عمق Depth: طول مسیر یک گره از ریشه تا آن گره را عمق درخت می‌گویند. عمق یک ریشه همیشه صفر است و برای مثال در درخت بالا، گره 19 در عمق یک است و برای گره 23 عمق آن 2 خواهد بود.

تعریف خود درخت Tree: درخت یک ساختار داده برگشتی recursive است که شامل گره‌ها و لبه‌ها، برای اتصال گره‌ها به یکدیگر است.

جملات زیر در مورد درخت صدق می‌کند:

  • هر گره می‌تواند فرزند نداشته باشد یا به هر تعداد که می‌خواهد فرزند داشته باشد.
  • هر گره یک والد دارد و تنها گره‌ای که والد ندارد، گره ریشه است (البته اگر درخت خالی باشد هیچ گره ای وجود ندارد).
  • همه گره‌ها از ریشه قابل دسترسی هستند و برای دسترسی به گره مورد نظر باید از ریشه تا آن گره، مسیری را طی کرد.
ار تفاع درخت Height: به حداکثر عمق یک درخت، ارتفاع درخت می‌گویند.
درجه گره Degree: به تعداد گره‌های فرزند یک گره، درجه آن گره می‌گویند. در درخت بالا درجه گره‌های 7 و 19 سه است. درجه گره 14 دو است و درجه برگ‌ها صفر است.
ضریب انشعاب Branching Factor: به حداکثر درجه یک گره در یک درخت، ضریب انشعاب آن درخت گویند.

پیاده سازی درخت

برای پیاده سازی یک درخت، از دو کلاس یکی جهت ساخت گره که حاوی اطلاعات است <TreeNode<T و دیگری جهت ایجاد درخت اصلی به همراه کلیه متدها و خاصیت هایش <Tree<T کمک می‌‌گیریم.

public class TreeNode<T>
{
    // شامل مقدار گره است
    private T value;
 
    // مشخص می‌کند که آیا گره والد دارد یا خیر
    private bool hasParent;
 
    // در صورت داشتن فرزند ، لیست فرزندان را شامل می‌شود
    private List<TreeNode<T>> children;
 
    /// <summary>سازنده کلاس </summary>
    /// <param name="value">مقدار گره</param>
    public TreeNode(T value)
    {
        if (value == null)
        {
            throw new ArgumentNullException(
                "Cannot insert null value!");
        }
        this.value = value;
        this.children = new List<TreeNode<T>>();
    }
 
    /// <summary>خاصیتی جهت مقداردهی گره</summary>
    public T Value
    {
        get
        {
            return this.value;
        }
        set
        {
            this.value = value;
        }
    }
 
    /// <summary>تعداد گره‌های فرزند را بر میگرداند</summary>
    public int ChildrenCount
    {
        get
        {
            return this.children.Count;
        }
    }
 
    /// <summary>به گره یک فرزند اضافه می‌کند</summary>
    /// <param name="child">آرگومان این متد یک گره است که قرار است به فرزندی گره فعلی در آید</param>
    public void AddChild(TreeNode<T> child)
    {
        if (child == null)
        {
            throw new ArgumentNullException(
                "Cannot insert null value!");
        }
 
        if (child.hasParent)
        {
            throw new ArgumentException(
                "The node already has a parent!");
        }
 
        child.hasParent = true;
        this.children.Add(child);
    }
 
    /// <summary>
    /// گره ای که اندیس آن داده شده است بازگردانده می‌شود
    /// </summary>
    /// <param name="index">اندیس گره</param>
    /// <returns>گره بازگشتی</returns>
    public TreeNode<T> GetChild(int index)
    {
        return this.children[index];
    }
}
 
/// <summary>این کلاس ساختار درخت را به کمک کلاس گره‌ها که در بالا تعریف کردیم میسازد</summary>
/// <typeparam name="T">نوع مقادیری که قرار است داخل درخت ذخیره شوند</typeparam>
public class Tree<T>
{
    // گره ریشه
    private TreeNode<T> root;
 
    /// <summary>سازنده کلاس</summary>
    /// <param name="value">مقدار گره اول که همان ریشه می‌شود</param>
    public Tree(T value)
    {
        if (value == null)
        {
            throw new ArgumentNullException(
                "Cannot insert null value!");
        }
 
        this.root = new TreeNode<T>(value);
    }
 
    /// <summary>سازنده دیگر برای کلاس درخت</summary>
    /// <param name="value">مقدار گره ریشه مثل سازنده اول</param>
    /// <param name="children">آرایه ای از گره‌ها که فرزند گره ریشه می‌شوند</param>
    public Tree(T value, params Tree<T>[] children)
        : this(value)
    {
        foreach (Tree<T> child in children)
        {
            this.root.AddChild(child.root);
        }
    }
 
    /// <summary>
    /// ریشه را بر میگرداند ، اگر ریشه ای نباشد نال بر میگرداند
    /// </summary>
    public TreeNode<T> Root
    {
        get
        {
            return this.root;
        }
    }
 
    /// <summary>پیمودن عرضی و نمایش درخت با الگوریتم دی اف اس </summary>
    /// <param name="root">ریشه (گره ابتدایی) درختی که قرار است پیمایش از آن شروع شود</param>
    /// <param name="spaces">یک کاراکتر جهت جداسازی مقادیر هر گره</param>
    private void PrintDFS(TreeNode<T> root, string spaces)
    {
        if (this.root == null)
        {
            return;
        }
 
        Console.WriteLine(spaces + root.Value);
 
        TreeNode<T> child = null;
        for (int i = 0; i < root.ChildrenCount; i++)
        {
            child = root.GetChild(i);
            PrintDFS(child, spaces + "   ");
        }
    }
 
    /// <summary>متد پیمایش درخت به صورت عمومی که تابع خصوصی که در بالا توضیح دادیم را صدا می‌زند</summary>
    public void TraverseDFS()
    {
        this.PrintDFS(this.root, string.Empty);
    }
}
 
/// <summary>
/// کد استفاده از ساختار درخت
/// </summary>
public static class TreeExample
{
    static void Main()
    {
        // Create the tree from the sample
        Tree<int> tree =
            new Tree<int>(7,
                new Tree<int>(19,
                    new Tree<int>(1),
                    new Tree<int>(12),
                    new Tree<int>(31)),
                new Tree<int>(21),
                new Tree<int>(14,
                    new Tree<int>(23),
                    new Tree<int>(6))
            );
 
        // پیمایش درخت با الگوریتم دی اف اس یا عمقی
        tree.TraverseDFS();
 
        // خروجی
        // 7
        //       19
        //        1
        //        12
        //        31
        //       21
        //       14
        //        23
        //        6
    }
}
کلاس TreeNode وظیفه‌ی ساخت گره را بر عهده دارد و با هر شیء‌ایی که از این کلاس می‌سازیم، یک گره ایجاد می‌کنیم که با خاصیت Children و متد AddChild آن می‌توانیم هر تعداد گره را که می‌خواهیم به فرزندی آن گره در آوریم که باز خود آن گره می‌تواند در خاصیت Children یک گره دیگر اضافه شود. به این ترتیب با ساخت هر گره و ایجاد رابطه از طریق خاصیت children هر گره درخت شکل می‌گیرد. سپس گره والد در ساختار کلاس درخت Tree قرار می‌گیرد و این کلاس شامل متدهایی است که می‌تواند روی درخت، عملیات پردازشی چون پیمایش درخت را انجام دهد.


پیمایش درخت به روش عمقی (DFS (Depth First Search

هدف از پیمایش درخت ملاقات یا بازبینی (تهیه لیستی از همه گره‌های یک درخت) تنها یکبار هر گره در درخت است. برای این کار الگوریتم‌های زیادی وجود دارند که ما در این مقاله تنها دو روش DFS و BFS را بررسی می‌کنیم.

روش DFS: هر گره‌ای که به تابع بالا بدهید، آن گره برای پیمایش، گره ریشه حساب خواهد شد و پیمایش از آن آغاز می‌گردد. در الگوریتم DFS روش پیمایش بدین گونه است که ما از گره ریشه آغاز کرده و گره ریشه را ملاقات می‌کنیم. سپس گره‌های فرزندش را به دست می‌آوریم و یکی از گره‌ها را انتخاب کرده و دوباره همین مورد را رویش انجام می‌دهیم تا نهایتا به یک برگ برسیم. وقتی که به برگی می‌رسیم یک مرحله به بالا برگشته و این کار را آنقدر تکرار می‌کنیم تا همه‌ی گره‌های آن ریشه یا درخت پیمایش شده باشند.

همین درخت را در نظر بگیرید:


 پیمایش درخت را از گره 7 آغاز می‌کنیم و آن را به عنوان ریشه در نظر می‌گیریم. حتی می‌توانیم پیمایش را از گره مثلا 19 آغاز کنیم و آن را برای پیمایش ریشه در نظر بگیریم ولی ما از همان 7 پیمایش را آغاز می‌کنیم:

ابتدا گره 7 ملاقات شده و آن را می‌نویسیم. سپس فرزندانش را بررسی می‌کنیم که سه فرزند دارد. یکی از فرزندان مثل گره 19 را انتخاب کرده و آن را ملاقات می‌کنیم (با هر بار ملاقات آن را چاپ می‌کنیم) سپس فرزندان آن را بررسی می‌کنیم و یکی از گره‌ها را انتخاب می‌کنیم و ملاقاتش می‌کنیم؛ برای مثال گره 1. از آن جا که گره یک، برگ است و فرزندی ندارد یک مرحله به سمت بالا برمی‌گردیم و برگ‌های 12 و 31 را هم ملاقات می‌کنیم. حالا همه‌ی فرزندان گره 19 را بررسی کردیم، بر می‌گردیم یک مرحله به سمت بالا و گره 21 را ملاقات می‌کنیم و از آنجا که گره 21 برگ است و فرزندی ندارد به بالا باز می‌گردیم و بعد گره 14 و فرزندانش 23 و 6 هم بررسی می‌شوند. پس ترتیب چاپ ما اینگونه می‌شود:

7-19-1-12-31-21-14-23-6


پیمایش درخت به روش (BFS (Breadth First Search 

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

7-19-21-14-1-12-31-23-6

اگر خوب دقت کنید می‌بینید که پیمایش سطحی است و هر سطح به ترتیب ملاقات می‌شود. به این الگوریتم، پیمایش موجی هم می‌گویند. دلیل آن هم این است که مثل سنگی می‌ماند که شما برای ایجاد موج روی دریاچه پرتاب می‌کنید.

برای این پیمایش از صف کمک گرفته می‌شود که مراحل زیر روی صف صورت می‌گیرد:

  • ریشه  وارد صف Q می‌شود.
  • دو مرحله زیر مرتبا تکرار می‌شوند:
  1. اولین گره صف به نام V را از Q در یافت می‌کنیم و آن را چاپ می‌کنیم.
  2. فرزندان گره V  را به صف اضافه می‌کنیم.
این نوع پیمایش، پیاده سازی راحتی دارد و همیشه نزدیک‌ترین گره‌ها به ریشه را می‌خواند و در هر مرحله گره‌هایی که می‌خواند از ریشه دورتر و دورتر می‌شوند.
مطالب دوره‌ها
مدیریت تغییرات گریدی از اطلاعات به کمک استفاده از الگوی واحد کار مشترک بین ViewModel و لایه سرویس
قالب پروژه WPF Framework به همراه چندین صفحه ابتدایی لازم، برای شروع هر برنامه‌ی تجاری دسکتاپی است؛ مثال مانند صفحه لاگین، صفحه تغییرات مشخصات کاربر وارد شده به سیستم و امثال آن. صفحه‌ای را که در این قسمت بررسی خواهیم کرد، صفحه تعریف کاربران جدید و ویرایش اطلاعات کاربران موجود است.


در این صفحه با کلیک بر روی دکمه به علاوه، یک ردیف به ردیف‌های موجود اضافه شده و در اینجا می‌توان اطلاعات کاربر جدیدی به همراه سطح دسترسی او را وارد و ذخیره کرد و یا حتی اطلاعات کاربران موجود را ویرایش نمود. اگر بخواهیم مانند مراحلی که در قسمت قبل در مورد تعریف یک صفحه جدید در برنامه توضیح داده شد، عمل کنیم، به صورت خلاصه به ترتیب ذیل عمل شده است:
1) ایجاد صفحه تغییر مشخصات کاربر
ابتدا صفحه Views\Admin\AddNewUser.xaml به پروژه ریشه که Viewهای برنامه در آن تعریف می‌شوند، اضافه شده است. به همراه دو دکمه و یک ListView که تطابق بهتری با قالب متروی مورد استفاده دارد.

2) تنظیم اعتبارسنجی صفحه اضافه شده
مرحله بعد تعریف هر صفحه‌ای در سیستم، مشخص سازی وضعیت دسترسی به آن است:
/// <summary>
/// افزودن و مدیریت کاربران سیستم
/// </summary>
[PageAuthorization(AuthorizationType.ApplyRequiredRoles, "IsAdmin, CanAddNewUser")]
ویژگی PageAuthorization به فایل Views\Admin\AddNewUser.xaml.cs اعمال شده است. در اینجا تنها کاربرانی که خاصیت‌های IsAdmin و CanAddNewUser آن‌ها true باشند، مجوز دسترسی به صفحه تعریف کاربران را خواهند یافت.

3) تغییر منوی برنامه جهت اشاره به صفحه جدید
در ادامه در فایل منوی برنامه Views\MainMenu.xaml تعریف دسترسی به صفحه Views\Admin\AddNewUser.xaml قید شده است:
                <Button Style="{DynamicResource MetroCircleButtonStyle}"
                        Height="55" Width="55"  
                        Command="{Binding DoNavigate}"
                        CommandParameter="\Views\Admin\AddNewUser.xaml"
                        Margin="2">
                    <Rectangle Width="28" Height="17.25">
                        <Rectangle.Fill>
                            <VisualBrush Stretch="Fill" Visual="{StaticResource appbar_user_add}" />
                        </Rectangle.Fill>
                    </Rectangle>
                </Button>
همانطور که در قسمت قبل نیز توضیح داده شده، تنها کافی است در اینجا CommandParameter را مساوی مسیر فایل AddNewUser.xaml قرار دهیم تا سیستم راهبری به صورت خودکار از آن استفاده کند.

4) ایجاد ViewModel متناظر با صفحه
مرحله نهایی تعریف صفحه AddNewUser، افزودن ViewModel متناظر با آن است که سورس کامل آن‌را در فایل ViewModels\Admin\AddNewUserViewModel.cs پروژه Infrastructure می‌توانید ملاحظه کنید.
نکته مهم این ViewModel، ارائه خاصیت لیست کاربران از نوع ObservableCollection به View و گرید برنامه است:
public ObservableCollection<User> UsersList { set; get; }
اطلاعات آن از IUsersService تزریق شده در سازنده کلاس ViewModel دریافت می‌شود:
        /// <summary>
        /// جهت مقاصد انقیاد داده‌ها در دبلیو پی اف طراحی شده است
        /// لیستی از کاربران سیستم را باز می‌گرداند
        /// </summary>
        /// <param name="count">تعداد کاربر مد نظر</param>
        /// <returns>لیستی از کاربران</returns>
        public ObservableCollection<User> GetSyncedUsersList(int count = 1000)
        {
            _users.OrderBy(x => x.FriendlyName).Take(count)
                  .Load();

            // For Databinding with WPF.
            // Before calling this method you need to fill the context by using `Load()` method.
            return _users.Local;
        }
این کدها را در فایل UsersService.cs لایه سرویس برنامه می‌توانید مشاهده نمائید.
در اینجا از قابلیت خاصیتی به نام Local که یک ObservableCollection تحت نظر EF را بازگشت می‌دهد، استفاده شده است. برای استفاده از این خاصیت، ابتدا باید کوئری خود را تهیه و سپس متد Load را بر روی آن فراخوانی کرد. سپس خاصیت Local بر اساس اطلاعات کوئری قبلی پر و مقدار دهی خواهد شد.
علت انتخاب نام Synced برای این متد، تحت نظر بودن اطلاعات خاصیت Local است تا زمانیکه Context تعریف شده زنده نگه داشته شود. به همین جهت در برنامه جاری از روش زنده نگه داشتن Context به ازای یک ViewModel استفاده شده است.
به Context، توسط اینترفیس IUnitOfWork تزریق شده در سازنده کلاس ViewModel می‌توان دسترسی یافت. چون در اینجا از تزریق وابستگی‌ها استفاده شده است، وهله‌ای که IUnitOfWork کلاس AddNewUserViewModel را تشکیل می‌دهد، دقیقا همان وهله‌ای است که در کلاس UsersService لایه سرویس استفاده شده است. در نتیجه، در گرید برنامه هر تغییری اعمال شود، تحت نظر IUnitOfWork خواهد بود و صرفا با فراخوانی متد uow.ApplyAllChanges آن، کلیه تغییرات تمام ردیف‌های تحت نظر EF به صورت خودکار در بانک اطلاعاتی درج و یا به روز خواهند شد.
همچنین در مورد ViewModelContextHasChanges نیز در قسمت قبل بحث شد. در اینجا پیاده سازی کننده آن صرفا خاصیت uow.ContextHasChanges است. به این ترتیب اگر کاربر، تغییری را در صفحه داده باشد و بخواهد به صفحه دیگری رجوع کند، با پیام زیر مواجه خواهد شد:


از همین خاصیت برای فعال و غیرفعال کردن دکمه ذخیره سازی اطلاعات نیز استفاده شده است:
  /// <summary>
  /// فعال و غیرفعال سازی خودکار دکمه ثبت
  /// این متد به صورت خودکار توسط RelayCommand کنترل می‌شود
  /// </summary>  
  private bool canDoSave()
  {
     // آیا در حین نمایش صفحه‌ای دیگر باید به کاربر پیغام داد که اطلاعات ذخیره نشده‌ای وجود دارد؟
     return ViewModelContextHasChanges;
  }
این متد توسط RelayCommand ایی به نام  DoSave
  /// <summary>
  /// رخداد ذخیره سازی اطلاعات را دریافت می‌کند
  /// </summary>
  public RelayCommand DoSave { set; get; }
که به نحو زیر مقدار دهی شده است، مورد استفاده قرار می‌گیرد:
DoSave = new RelayCommand(doSave, canDoSave);
به ازای هر تغییری در UI، این RelayCommand به نتیجه canDoSave مراجعه کرده و اگر خروجی آن true باشد، دکمه متناظر را به صورت خودکار فعال می‌کند و یا برعکس.
این بررسی نیز بسیار سبک و سریع است. از این جهت که تغییرات Context در حافظه نگهداری می‌شوند و مراجعه به آن مساوی مراجعه به بانک اطلاعاتی نیست.
مطالب دوره‌ها
آشنایی با AOP IL Weaving
IL Weaving در AOP به معنای اتصال Aspects تعریف شده، پس از کامپایل برنامه به فایل‌های باینری نهایی است. اینکار با ویرایش اسمبلی‌ها در سطح IL یا کد میانی صورت می‌گیرد. بنابراین در این حالت دیگر یک محصور کننده و پروکسی، در این بین جهت مزین سازی اشیاء، در زمان اجرای برنامه تشکیل نمی‌شود. بلکه فراخوانی Aspects به معنای فراخوانی واقعی قطعه کدهایی است که به اسمبلی‌های برنامه پس از کامپایل آن‌ها تزریق شده‌اند.
در دنیای دات نت، ابزارهای چندی امکان انجام IL Weaving را فراهم ساخته‌اند که تعدادی از آن‌ها به قرار ذیل هستند:
- PostSharp
- LOOM.NET
- Wicca
و ...

در بین این‌ها، PostSharp معروفترین فریم ورک AOP بوده و در ادامه از آن استفاده خواهیم کرد.


پیشنیاز ادامه بحث

ابتدا یک پروژه کنسول جدید را آغاز کرده و سپس در خط فرمان پاور شل نوگت در VS.NET دستور ذیل را اجرا کنید:
 PM> Install-Package PostSharp
به این ترتیب ارجاعی به PostSharp به پروژه جاری اضافه خواهد شد. البته حجم آن نسبتا بالا است؛ نزدیک به 20 مگ به همراه ابزارهای تزریق کد همراه با آن. مجوز استفاده از آن نیز تجاری و مدت دار است.


مراحل ایجاد یک Aspect برای پروسه IL Code Weaving

ابتدا یک کلاس پایه مشتق شده از کلاسی ویژه موجود در یکی از فریم ورک‌های AOP باید تعریف شود. مرحله بعد، کار اتصال این Aspect می‌باشد که توسط پردازشگر ثانویه IL Code Weaving انجام می‌شود.
در ادامه قصد داریم همان مثال LoggingInterceptor قسمت دوم این سری را با استفاده از IL Code Weaving پیاده سازی کنیم.
using System;

namespace AOP03
{
    public class MyType
    {
        public void DoSomething(string data, int i)
        {
            Console.WriteLine("DoSomething({0}, {1});", data, i);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            new MyType().DoSomething("Test", 1);
        }
    }
}
کدهای برنامه همانند قبل است. اما اینبار بجای استفاده از Interceptors، با ارث بری از کلاس OnMethodBoundaryAspect کتابخانه PostSharp شروع خواهیم کرد:
using System;
using PostSharp.Aspects;

namespace AOP03
{
    [Serializable]
    public class LoggingAspect : OnMethodBoundaryAspect
    {
        public override void OnEntry(MethodExecutionArgs args)
        {
            Console.WriteLine("On Entry");
        }

        public override void OnExit(MethodExecutionArgs args)
        {
            Console.WriteLine("On Exit");
        }

        public override void OnSuccess(MethodExecutionArgs args)
        {
            Console.WriteLine("On Success");
        }

        public override void OnException(MethodExecutionArgs args)
        {
            Console.WriteLine("On Exception");
        }
    }
}
نیاز است این کلاس توسط ویژگی Serializable مزین شود تا توسط PostSharp قابل استفاده گردد. همانطور که ملاحظه می‌کنید، مراحل مختلف اجرای یک Aspcet در اینجا با override متدهای کلاس پایه OnMethodBoundaryAspect پیاده سازی شده‌اند. این مراحل را پیشتر در زمان استفاده از Interceptors توسط try/finally/catch بررسی کرده بودیم.
اکنون اگر برنامه را اجرا کنیم، اتفاق خاصی رخ نداده و همان خروجی معمول متد DoSomething در کنسول نمایش داده خواهد شد. بنابراین در مرحله بعد نیاز است تا این Aspect را به کدهای برنامه متصل کنیم.
کلاس OnMethodBoundaryAspect در کتابخانه PostSharp، از کلاس MulticastAttribute مشتق می‌شود. بنابراین LoggingAspect ایی را که ایجاد کرده‌ایم نیز می‌توان به صورت یک ویژگی به متد‌های مورد نظر خود افزود:
    public class MyType
    {
        [LoggingAspect]
        public void DoSomething(string data, int i)
        {
            Console.WriteLine("DoSomething({0}, {1});", data, i);
        }
    }
اکنون اگر برنامه را اجرا کنیم، با خروجی زیر مواجه خواهیم شد:
 On Entry
DoSomething(Test, 1);
On Success
On Exit
برای اینکه بتوان عملیات رخ داده را بهتر توضیح داد می‌تواند از یک دی‌کامپایلر مانند برنامه معروف Reflector استفاده کرد:
public void DoSomething(string data, int i)
{
    <>z__Aspects.a0.OnEntry(null);
    try
    {
        Console.WriteLine("DoSomething({0}, {1});", data, i);
        <>z__Aspects.a0.OnSuccess(null);
    }
    catch (Exception)
    {
        <>z__Aspects.a0.OnException(null);
        throw;
    }
    finally
    {
        <>z__Aspects.a0.OnExit(null);
    }
}
این کدی است که به صورت پویا توسط PostSharp به اسمبلی نهایی فایل اجرایی برنامه تزریق شده است.

خوب! این یک روش اتصال Aspects به برنامه است. اما اگر همانند Interceptors بخواهیم Aspect تعریف شده را سراسری اعمال کنیم چکار باید کرد (بدون نیاز به قرار دادن ویژگی بر روی تک تک متدها)؟
برای اینکار ابتدا نیاز است میدان عملکرد Aspect تعریف شده را توسط ویژگی MulticastAttributeUsage محدود کنیم تا برای مثال به خواص اعمال نشوند:
 [Serializable]
[MulticastAttributeUsage(MulticastTargets.Method, TargetMemberAttributes = MulticastAttributes.Instance)]
public class LoggingAspect : OnMethodBoundaryAspect
سپس فایل AssemblyInfo.cs استاندارد پروژه را گشوده و سطر زیر را به آن اضافه کنید:
 [assembly: LoggingAspect(AttributeTargetTypes = "AOP03.*")]
توسط AttributeTargetTypes می‌توان اعمال این Aspect را به یک فضای نام خاص نیز محدود کرد.

مزیت روش IL Code Weaving نسبت به Interceptors، کارآیی و سرعت بالاتر است. از این جهت که کدهایی که قرار است اجرا شوند، پیشتر در اسمبلی برنامه قرار گرفته‌اند و نیازی نیست تا در زمان اجرا، کدی به برنامه به صورت پویا تزریق گردد.
مطالب
تست کردن Command/Command Handler ها در MediatR
شاید شما هم در پروژه خودتان نیاز داشته باشید تا اتصالات MediatR را بررسی یا به نوعی از صحت کدهایی که بر پایه MediatR زدید مطمئن شوید. در اینجا به بررسی نحوه Assert کردن اتصالات MediatR می‌پردازم. اول از همه باید به این فکر کرد که چه چیزی را می‌خواهیم تست کنیم؟ طبیعتا وقتی یک Command را ایجاد میکنیم، انتظار داریم که یک CommandHandler مختص به آن نیز ایجاد شده باشد. پس ما به بررسی ساختار کد می‌پردازیم.
برای شروع، یک Interface را با متد IsValid را ایجاد میکنیم. این Interface بعد‌ها توسط کلاس CommandValidator پیاده سازی می‌شود تا هر وظیفه، تشخیص درست بودن اتصالات command را چک کنند.
همانطور که مشخص است، متد IsValid دو پارامتر اسم ارسال کننده (Command,Query,Notification) و اسم Handler این ارسال کننده‌ها را دریافت میکند.
internal interface IValidation
    {
        bool IsValid(string sendNamesEndTo, string handlerNamesEndTo);
    }
کد زیر نحوه پیاده سازی IValidation را برای کلاس CommandValidator، نشان میدهد:
  public bool IsValid(string commandNamesEndTo = "Command", string commandHandlersEndTo = "CommandHandler")
        {
            var assemblies = AppDomain.CurrentDomain.GetAssemblies();

            var commandTypeInfos = assemblies.SelectMany(x => x.DefinedTypes.Where(typeInfo =>
                typeInfo.Name.ToLower().EndsWith(commandNamesEndTo.ToLower()) &&
                typeInfo.ImplementedInterfaces.Any(type => type == typeof(IBaseRequest))));

            var memberInfos = commandTypeInfos as TypeInfo[] ?? commandTypeInfos.ToArray();
            if (!memberInfos.Any())
                throw new ArgumentException("Can not find any Command");

            var handlerTypeInfo = assemblies.SelectMany(x => x.DefinedTypes.Where(typeInfo =>
                typeInfo.Name.ToLower().EndsWith(commandHandlersEndTo.ToLower())));

            var typeInfos = handlerTypeInfo as TypeInfo[] ?? handlerTypeInfo.ToArray();
            if (!typeInfos.Any())
                throw new ArgumentException("Can not find any CommandHandler");

            if (typeInfos.Count() != memberInfos.Count())
                return false;

            return !(from typeInfo in memberInfos
                let interfaces = typeInfos.SelectMany(x => x.ImplementedInterfaces)
                where interfaces.Any(x => x.GenericTypeArguments.All(type => type != typeInfo))
                select typeInfo).Any();
        }
همانطور که مشخص است، برای دو پارامتر ورودی، مقادیر پیش فرض Command و CommandHandler در نظر گرفته شده‌اند. توجه داشته باشید این اسم کامل نیست و تنها نام انتهای کلاس است: برای مثال OrderCommand/OrderCommandHandler:
public bool IsValid(string commandNamesEndTo = "Command", string commandHandlersEndTo = "CommandHandler")
داخل بدنه متد، ابتدا تمام Assembly‌های موجود در App را لیست میکنیم :
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
سپس تمام کلاس‌های تعریف شده‌ای را که از IBaseRequest ارث بری کرده و انتهای نام آن‌ها شامل commandNamesEndTo باشد، لیست می‌کند. می‌توان گفت تا اینجا تمام Command‌ها را پیدا کرده‌ایم. در صورتیکه لیست موجود خالی باشد، یعنی یک‌جای کار مشکل دارد؛ شاید کلا Command ای تعریف نشده یا ... پس یک ArgumentException را throw میکنیم.
            var commandTypeInfos = assemblies.SelectMany(x => x.DefinedTypes.Where(typeInfo =>
                typeInfo.Name.ToLower().EndsWith(commandNamesEndTo.ToLower()) &&
                typeInfo.ImplementedInterfaces.Any(type => type == typeof(IBaseRequest))));

            var memberInfos = commandTypeInfos as TypeInfo[] ?? commandTypeInfos.ToArray();
            if (!memberInfos.Any())
                throw new ArgumentException("Can not find any Command");
در مرحله‌ی بعدی، تمام Handler‌ها را پیدا و لیست میکنیم. در اینجا نیز مانند کد بالا، اگر لیست خالی باشد، به این معناست که هیچ handler ای تعریف نشده‌است.
  var handlerTypeInfo = assemblies.SelectMany(x => x.DefinedTypes.Where(typeInfo =>
                typeInfo.Name.ToLower().EndsWith(commandHandlersEndTo.ToLower())));

            var typeInfos = handlerTypeInfo as TypeInfo[] ?? handlerTypeInfo.ToArray();
            if (!typeInfos.Any())
                throw new ArgumentException("Can not find any CommandHandler");
مرحله بعدی، بررسی کردن تعداد Command‌ها و CommandHandler‌ها می‌باشد. طبیعتا باید مقدار این دو برابر باشند؛ چرا که هر Command، نیاز به یک  CommandHandler نیز دارد. بنابراین اگر تعداد این دو یکسان نبود، ساختار ما درست نیست؛ پس مقدار false برگشت داده می‌شود : 
    if (typeInfos.Count() != memberInfos.Count())
                return false;
بعد از تمام این ماجرا‌ها، به مرحله آخر میرسیم؛ آن هم چک کردن نظیر به نظیر Command‌ها با CommandHandler‌ها می‌باشد. به این صورت که اگر به ازای هر Command تعریف شده یک CommandHandler با GenericTypeArguments از نوع command وجود داشته باشد، می‌توان گفت که ساختار ما درست تعریف شده‌است.
return !(from typeInfo in memberInfos
                let interfaces = typeInfos.SelectMany(x => x.ImplementedInterfaces)
                where interfaces.Any(x => x.GenericTypeArguments.All(type => type != typeInfo))
                select typeInfo).Any();
توجه داشته باشید برای تست کردن قسمت‌هایی مثل Query و Notification نیز می‌توان از همین فرآیند بهره برد؛ البته با کمی تغییر در پیاده سازی متد IsValid.
در نهایت برای استفاده می‌توان به شکل زیر کد‌ها را استفاده کرد : 
var validCommandConfiguration = new CommandValidator().IsValid();

برای دیدن کد‌های کامل پیاده سازی تست Command/Query/Notification، می‌توانید از لینک گیت هاب زیر استفاده کنید.