مطالب
اشیاء تغییر ناپذیر (Immutable Object)
کلمه‌ی mutable به معنای تغییر پذیر و کلمه‌ی immutable به معنای تغیر ناپذیر در زبان انگلیسی تعریف شده‌اند. در دنیای IT این دو واژه نیز همین معنا را دارند. بطور مثال: یک رشته‌ی mutable، یعنی رشته‌ای که بتوان آن را تغییر داد و یک رشته‌ی immutable یعنی رشته‌ای که غیر قابل تغییر است.



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

تعریف اشیاء تغییر ناپذیر (Immutable Objects) :
این اشیاء، اشیائی هستند که بعد از بارگزاری در حافظه، به هیچ وجه نمی‌توانند اصلاح و یا تغییر کنند. نه از طریق خارجی (کاربران External) و نه از طریق داخلی (اعضای کلاس Internal).

چه زمانی از این اشیاء استفاده می‌کنیم؟
اشیاء تغییر ناپذیر برای داده‌های استاتیک استفاده می‌شوند و نمونه‌هایی از آن در بخش زیر لیست شده‌اند:

 • داده‌های اصلی (Master Data): یکی از بیشترین کاربرد‌های اشیاء تغییر ناپذیر، برای بارگذاری داده‌های اصلی است (کشور‌ها، واحد‌های پولی، استان ها) و داده‌هایی که به ندرت تغییر می‌کنند. این داده‌های اصلی بعد از بارگزاری در حافظه، دیگر تغییر نخواهند کرد.

 • اطلاعات پیکره‌بندی (Configuration Data): همه‌ی برنامه‌ها نیاز به اطلاعات پیکره‌بندی دارند. در دنیای برنامه‌های مایکروسافت، عموما این اطلاعات پیکره بندی را در فایل‌های web.config و App.config ذخیره می‌کنیم. این نوع اطلاعات بصورت یک شیء در حافظه بارگذاری می‌شوند و بعدا تغییر نخواهند کرد.

 • اشیاء Singleton: اشیاء singleton اشیائی هستند که تنها یک نمونه از آنها را می‌توان ایجاد کرد. در برنامه‌ها از این اشیاء برای اشتراک گذاشتن اطلاعات استاتیک استفاده می‌کنند. اگر این اطلاعات تغییر نکنند، یکی از گزینه‌ها، استفاده از اشیاء تغییر ناپذیر هستند.

چگونه می‌توان در سی شارپ اشیاء تغییر ناپذیر را ایجاد کنیم؟

اشیاء تغییر ناپذیر (immutable objects) تنها توسط کلاس‌های تغییر ناپذیر (immutable classes) می‌توانند ایجاد شوند.
برای ایجاد کلاسی تغییر ناپذیر، سه مرحله باید طی شود:
 1- حذف بلاک Set: همانطور که گفته شد، بخش Set از property ‌ها باید حذف شود. با حذف این بخش بعد از بارگزاری شیء در حافظه، دیگر نمی‌توان آن را تغییر داد:
public class Currency
{
  private string _currencyName;
  private string _countryName;

  public string CurrencyName
  {
     get { return _currencyName; }
  }

  public string CountryName
  {
     get { return _countryName; }
  }
}

 2- مهیا کردن پارامتر‌ها از طریق سازنده‌ی کلاس: با حذف بلاک set، راهی برای بارگزاری اطلاعات، در کلاس وجود ندارد. از این رو می‌توان از طریق پارامتر‌های سازنده‌ی کلاس، اطلاعات را به شیء ارسال کرد.
  private string _currencyName;
  private string _countryName;
  public Currency(string paramCurrencyName,string paramCountryName)
  {
     _currencyName= paramCurrencyName;
     _countryName = paramCountryName;
  }

 3- تعریف متغیر‌های کلاس به صورت فقط خواندنی READONLY
در تعریف اولیه گفته شد که اشیاء immutable نه از طریق خارجی (کاربر) و «کمی فراتر» نه از طریق داخلی (اعضای کلاس) قابل تغییر نیستند. اما کلاس ایجاد شده را می‌توان بعد از ایجاد نمونه‌ای از آن، مجددا تغییر داد. کافی است یک متد به شکل زیر در آن تعریف کنیم و به‌راحتی وضعیت شیء را از طریق آن تغییر دهیم.
  public void DoSomthing()
  {
     _countryName = "somthing else";
  }

راه حل ارائه شده‌ی برای حل این موضوع، معرفی متغیر‌ها به صورت readonly می‌باشد. متغیرهایی که بصورت فقط خواندنی تعریف می‌شوند، تنها از طریق سازنده‌ی شیء می‌توانند مقداردهی اولیه شوند.
باز طراحی نهایی کلاس Currency  به صورت زیر است:
 public class Currency
{
  private readonly string _currencyName;
  private readonly string _countryName;
  public string CurrencyName
  {
    get { return _currencyName; }
  }

  public string CountryName
  {
    get { return _countryName; }
  }
  public Currency(string paramCurrencyName,string paramCountryName)
  {
     _currencyName= paramCurrencyName;
     _countryName = paramCountryName;
  }
}
نظرات مطالب
C# 7 - Ref Returns and Ref Locals
یک نکته‌ی تکمیلی: امکان تعریف خروجی از نوع ref readonly در C# 7.2

modifier جدیدی در C# 7.2 به نام ref readonly جهت تعریف نوع خروجی متدها نیز معرفی شده‌است. به این ترتیب یک متد می‌تواند بازگشت ارجاعی به اطلاعاتی موجود را بیان و همچنین فراخوان را از تغییر آن منع کند.
البته فراخوان می‌تواند تصمیم گیری کند که آیا یک کپی و یا یک ارجاع فقط خواندنی را از این متد ویژه دریافت کند. به این معنا که خروجی از نوع ref readonly، فراخوان را ملزم به تعریف یک متغیر محلی از نوع ref readonly نمی‌کند.

در مثال زیر، متد ReturnBiggestA یک خروجی کپی را باز می‌گرداند و متد ReturnBiggestARefReadonly دقیقا ارجاعی را به DataInfo اصلی بازگشت می‌دهد و با آن یکی است:
namespace CS72Tests
{
    public struct DataInfo
    {
        public double A;
    }

    public class RefReadonlyExamples
    {
        public DataInfo ReturnBiggestA(in DataInfo data1, in DataInfo data2)
        {
            return data1.A > data2.A ? data1 : data2;
        }

        public ref readonly DataInfo ReturnBiggestARefReadonly(in DataInfo data1, in DataInfo data2)
        {
            if (data1.A > data2.A)
            {
                return ref data1;
            }
            return ref data2;
        }

        public void TestingRefReadonly()
        {
            var data1 = new DataInfo { A = 0 };
            var data2 = new DataInfo { A = 100 };

            var biggest = ReturnBiggestA(data1, data2);
            biggest.A = 42;


            var biggest2 = ReturnBiggestARefReadonly(data1, data2);
            biggest2.A = 99;


            ref readonly var biggest3 = ref ReturnBiggestARefReadonly(data1, data2);
            biggest3.A = 99; // ERROR: The left-hand side of an assignment must be a variable, property or indexer
        }
    }
}
- در این فراخوانی‌ها، biggest یک کپی از data2 را باز می‌گرداند. به همین جهت می‌توان A آن‌را تغییر داد.
- در اولین فراخوانی ReturnBiggestARefReadonly، با تعریف خروجی به صورت var biggest2، یک کپی از data2 را دریافت کرده‌ایم. به همین جهت A آن قابل تغییر است.
- اما در دومین فراخوانی ReturnBiggestARefReadonly، چون خروجی آن‌را از نوع ref readonly var دریافت کرده‌ایم، این خروجی به data2 اصلی اشاره می‌کند و همچنین فقط خواندنی است. بنابراین سطر بعدی آن که A را تغییر می‌دهد، مجاز نیست.

پ.ن
در ابتدا قصد داشتند ref readonly را برای تعریف پارامترهای value type نیز بکار برند، اما این تصمیم با معرفی پارامترهای از نوع in جایگزین شد. به همین جهت ممکن است مقالات قدیمی‌تر C# 7.2 را با تعریف متدهایی مانند ذیل نیز مشاهده کنید که در نگارش آخر C# 7.2، تمام این‌ها به in تغییر کرده‌اند:
public static void Add(ref readonly int x, ref readonly int y, ref int z)
{
   z = x + y + z;
}
مطالب
الگوی Service Locator
الگوی Service Locator، به صورت گسترده‌ای به عنوان یک ضد الگو شناخته می‌شود و هنگامیکه از این الگو استفاده می‌کنیم ما را با یک سری از مشکلات رو به رو می‌کند. ولی این الگوی طراحی به خودی خود منشاء مشکل نیست. مشکل اصلی این الگو نحوه استفاده از آن است که در این مقاله درباره آن بحث می‌کنیم. 

مشکل اصلی الگوی Service Locator
زمانیکه یک کلاس، وابسته به یک Service Locator است، آن تمام وابستگی‌های واقعی کلاس را مخفی می‌کند.
 ما نمی‌توانیم وابستگی‌ها را با نگاه کردن به تعریف سازنده‌ی کلاس بیان کنیم. در عوض، ما باید کلاس و شاید مشارکت کنندگانش را بخوانیم تا برای تشخیص اینکه چه کلاس‌های دیگری برای کار آنها لازم است. 
فرض کنید ما یک کارخانه تولید ماشین را مدل می‌کنیم. کارخانه، ماشین‌ها را تولید می‌کند و آنها را به مکان فروش می‌رساند:
class Car
{

}

class CarProducer
{
    public void DeliverTo(int carsCount, string town)
    {
        Car[] cars = new Car[carsCount];
        ...
    }
}
در حال حاضر سازنده نیاز به کمک یک نهاد دیگر حمل کننده دارد که به آن کمک می‌کند تا اتومبیل را به محل مشخص شده ارسال کند: 
class Transporter
{

    public string Name { get; private set; }

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

    public void Deliver(Car[] cars, string town)
    {
        Console.WriteLine("Delivering {0} car(s) to {1} by {2}",
                            cars.Length, town, this.Name);
    }
}
چگونه می‌توانیم تولید کننده را در این راه حل ملاقات کنیم؟ یک راه برای رسیدن به آن این است که از Service Locator استفاده کنید:
static class TransporterLocator
{
    static IList<Transporter> transporters = new List<Transporter>();

    public static void Register(Transporter transporter)
    {
        transporters.Add(transporter);
    }

    public static Transporter Locate(string name)
    {
        return
            transporters
                .Where(transporter => transporter.Name == name)
                .Single();
    }
}
این کلاس استاتیک است که مجموعه‌ای از حمل کننده‌های موجود را در آن نگهداری می‌کند و هر حمل کننده به واسطۀ نام آن شناسایی می‌شود. بنابراین زمانیکه مشتری (تولید کننده خودرو در این مورد) نیاز به یک حمل کننده دارد، فقط باید نام آن را صدا بزند:
class CarProducer
{
    public void DeliverTo(int carsCount, string town)
    {
        Car[] cars = new Car[carsCount];

        Transporter transporter = null;
        if (carsCount <= 12)
            transporter = TransporterLocator.Locate("truck");
        else
            transporter = TransporterLocator.Locate("train");

        transporter.Deliver(cars, town);

    }
}

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

شناسایی مشکلات Service Locator
برای درک مشکلات راه حل قبلی، باید سعی کنیم تا از آن استفاده کنیم:
TransporterLocator.Register(new Transporter("truck"));
TransporterLocator.Register(new Transporter("train"));

CarProducer producer = new CarProducer();
producer.DeliverTo(7, "Tehran");
producer.DeliverTo(74, "Tehran");
همانطور که می‌بینید، ما نمی‌توانیم از کلاس CarProducer استفاده کنیم، اگر قبل از آن، مکان را مشخص نکرده باشیم. کلاس CarProducer مستقل نیست و یکی از اصول اساسی طراحی نرم افزار را نقض می‌کند: اگر ما یک ارجاع به یک شیء داشته باشیم، آن شیء به درستی تعریف شده است. اگر ما قبل از استفاده از کلاس CarProducer محل آن را مشخص نکرده باشیم، عملیات با خطا مواجه خواهد شد: 
TransporterLocator.Register(new Transporter("truck"));

CarProducer producer = new CarProducer();
producer.DeliverTo(7, "Tehran");
producer.DeliverTo(74, "Tehran");
این قطعه از کد دارای خطاست؛ زیرا انتظار دارد قطار در Service Locator ثبت شده باشد. به صورت خلاصه همان شیء ممکن است به درستی کار کند یا با خطا رو به رو شود.
بهتر است که کلاس CarProducer را به گونه‌ای طراحی کنید که اگر اشیای مورد نیاز آن به درستی تنظیم نشده باشند، آنگاه نتوان از آن نمونه سازی کرد.

 حذف Service Locator
اگر ما ارجاعی را به یک شیء داشته باشیم، می‌خواهیم مطمئن باشیم که این شیء به خوبی تشکیل شده است و ما نمی‌خواهیم با یک سری از خطا‌های اولیه که از نیازهای اولیه شیء می‌باشند، مواجه شویم. یکی از راه‌ها برای حل این مشکل آن است که تمام وابستگی‌های اجباری  آن‌را در سازنده کلاس تعریف کنیم. به این ترتیب، اگر وابستگی‌ها در دسترس نباشند، راهی قانونی برای ساخت یک شیء وجود نخواهد داشت.
class CarProducer
{
    private Transporter truck;
    private Transporter train;

    public CarProducer(Transporter truck, Transporter train)
    {
        if (truck == null)
            throw new ArgumentNullException("truck");

        if (train == null)
            throw new ArgumentNullException("train");

        this.truck = truck;
        this.train = train;
    }

    public void DeliverTo(int carsCount, string town)
    {
        Car[] cars = new Car[carsCount];
        Transporter transporter = this.truck;
        if (carsCount > 12)
            transporter = this.train;

        transporter.Deliver(cars, town);
    }
}
در این پیاده سازی، CarProducer نیاز به تمام وابستگی‌های خود را دارد و به هیچ عنوان نمی‌توان از کلاس carProducer وهله‌ای ساخت، تا زمانیکه وابستگی‌های آن را مشخص کرده باشیم. حتی بیشتر از آن، در پیاده سازی سازنده با دو شرط محافظ آغاز می‌شود. اگر هر یک از دو حمل کننده تهی باشند، سازنده CarProducer یک استثناء را بر می‌گرداند و شیء ساخته نخواهد شد. با استفاده از این پیاده سازی، مطمئن هستیم که شیء موجود معتبر است که یک مفهوم بسیار مهم است که ما را از وضعیت ناپایدار در سیستم، در امان نگه می‌دارد.

آیا وضعیتی وجود دارد که در آن Service Locator  یک راه حل قابل قبول باشد؟

در برخی موارد بجای اینکه وابستگی‌ها را به صورت صریح قید کنیم، بهتر است از این الگو استفاده کنیم.
این مثال را میتوان از زوایای مختلفی مورد بررسی قرار داد:
    1)  ما نمی‌توانیم با نگاه کردن به پیاده سازی کلاس بفهمیم که چه شرایطی قبل از نمونه سازی از کلاس باید رعایت شده باشند.
    2) ما نمی‌توانیم بدانیم زمانیکه یک متد فراخوانی می‌شود، عملیات به درستی به انجام می‌رسد و یا با خطا رو به رو می‌شود.
    3) ما نمی‌توانیم این کلاس را در یک تست بررسی کنیم؛ زیرا آن کلاس وابسته به اشیاء مبهمی هست که در جای دیگری تنظیم شده‌اند. 
همه این مسائل جدی هستند. با این دلایل است که Service Locator به عنوان یک ضد الگو در نظر گرفته شده است. اما ... این ضد الگوی در کدها شیء گرا است. اما تمام کد‌های ما شیء گرا نیستند. 
زمانیکه ما از یک پایگاه داده رابطه‌ای در حال استفاده هستیم، منطق Persistence از حالت شیء گرایی خود خارج می‌شود. منطق Persistence به صورت عمده‌ای برای نگاشت مدل‌های داده به جداول است. منطق رابط کاربری ( User Interface ) نیز شیء گرا نیست؛ زیرا عمدتا از نگاشت بین داده ساده و عناصر رابط کاربر تشکیل شده‌است.
در نتیجه، عنصر مشترک در هر دو مورد، نگاشت است و این دقیقا همان چیزی است که Service Locator انجام می‌دهد؛ نگاشت کلید‌ها به اشیاء. پس چرا ما نباید از Service Locator در لایه‌هایی که عمدتا شیء گرا نیستند استفاده کنیم؟
 
نتیجه گیری
در این مقاله ما به الگویی پرداختیم که در عمل به صورت گسترده‌ای از آن اجتناب می‌شود. مشکل Service Locator این است که اصول طراحی شیء گرا را نقض می‌کند. اما در عین حال، مناطقی از کد وجود دارند که طبیعت آنها شیء گرا نیستند. لایه‌های Presentation و persistence شیء گرا نیستند. در عوض، آنها در حال نگاشت مدل به چیزهای دیگری، جداول و ستون در پایگاه داده و یا عناصر رابط کاربری هستند. اینها مکان هایی هستند که الگوی طراحی Service Locator را می‌توان با خیال راحت و بدون نقض هر یک از دستورالعمل‌های شیء گرایی، صرفا به این دلیل که این مکان‌ها به هیچ وجه شیء گرا نیستند، استفاده کرد.
مطالب دوره‌ها
آشنایی با مدل برنامه نویسی TAP
تاریخچه‌ی اعمال غیر همزمان در دات نت فریم ورک

دات نت فریم ورک، از زمان ارائه نگارش یک آن، از اعمال غیرهمزمان و API خاص آن پشتیبانی می‌کرده‌است. همچنین این مورد یکی از ویژگی‌های Win32 نیز می‌باشد. نوشتن کدهای همزمان متداول بسیار ساده است. در این نوع کدها هر عملیات خاص، پس از پایان عملیات قبلی انجام می‌شود.
        public string TestNoneAsync()
        {
            var webClient = new WebClient();
            return webClient.DownloadString("http://www.google.com");
        }
در این مثال متداول، متد DownloadString به صورت همزمان یا synchronous عمل می‌کند. به این معنا که تا پایان عملیات دریافت اطلاعات از وب، منتظر مانده و ترد جاری را قفل می‌کند. مشکل از جایی آغاز می‌شود که مدت زمان دریافت اطلاعات، طولانی باشد. چون این عملیات در ترد UI در حال انجام است، کل رابط کاربری برنامه تا پایان عملیات نیز قفل شده و دیگر پاسخگوی سایر اعمال رسیده نخواهد بود. در این حالت عموما ویندوز در نوار عنوان برنامه، واژه‌های Not responding را نمایش می‌دهد.
این مورد همچنین در برنامه‌های سمت سرور نیز حائز اهمیت است. با قفل شدن تعداد زیادی ترد در حال اجرا، عملا قدرت پاسخ‌دهی سرور نیز کاهش می‌یابد. بنابراین در این نوع موارد، برنامه‌های چند ریسمانی هرچند در سمت کلاینت ممکن است مفید واقع شوند و برای مثال ترد UI را آزاد کنند، اما اثر آنچنانی بر روی برنامه‌های سمت سرور ندارند. زیرا در آن‌ها می‌توان هزاران ترد را ایجاد کرد که همگی دارای کدهای اصطلاحا blocking باشند. برای حل این مساله استفاده از API غیرهمزمان توصیه می‌شود.
برای نمونه کلاس WebClient توکار دات نت، دارای متدی به نام DownloadStringAsync نیز می‌باشد. این متد به محض فراخوانی، ترد جاری را آزاد می‌کند. به این معنا که فراخوانی آن سبب توقف ترد جاری برای دریافت نتیجه‌ی دریافت اطلاعات از وب نمی‌شود. به این نوع API، یک Asynchronous API گفته می‌شود؛ زیرا با سایر کدهای نوشته شده، هماهنگ و همزمان اجرا نمی‌شود.
هر چند این کد جدید مشکل عدم پاسخ دهی برنامه را برطرف می‌کند، اما مشکل دیگری را به همراه دارد؛ چگونه باید حاصل عملیات آن‌را پس از پایان کار دریافت کرد؟ چگونه باید خطاها و مشکلات احتمالی را مدیریت کرد؟
برای مدیریت این مساله، رخدادی به نام DownloadStringCompleted تعریف شده‌است. روال رویدادگردان آن پس از پایان کار دریافت اطلاعات از وب، فراخوانی می‌گردد.
        public void TestAsync()
        {
            var webClient = new WebClient();
            webClient.DownloadStringAsync(new Uri("http://www.google.com"));
            webClient.DownloadStringCompleted += webClientDownloadStringCompleted;
        }

        void webClientDownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            // use e.Result
        }
در اینجا همچنین توسط آرگومان DownloadStringCompletedEventArgs، موفقیت یا شکست عملیات نیز گزارش می‌شود و مقدار e.Result حاصل عملیات است.

مشکل!
ما سادگی یک عملیات همزمان را از دست دادیم. متد TestNoneAsync از لحاظ پیاده سازی و همچنین خواندن و نگهداری آن در طول زمان، بسیار ساده‌تر است از نمونه‌ی TestAsync نوشته شده. در کدهای غیرهمزمان فوق، یک متد ساده، به دو متد مجزا خرد شده‌است و نتیجه‌ی نهایی، درون یک روال رخدادگردان بدست می‌آید.
به این مدل، EAP یا Event based asynchronous pattern نیز گفته می‌شود. EAP در دات نت 2 معرفی شد. روال‌های رخدادگردان در این حالت، در ترد اصلی برنامه اجرا می‌شوند. اما اگر به حالت اصلی اعمال غیرهمزمان موجود از دات نت یک کوچ کنیم، اینطور نیست. در WinForms و WPF برای به روز رسانی رابط کاربری نیاز است اطلاعات دریافت شده در همان تردی که رابط کاربری ایجاد شده است، تحویل گرفته شده و استفاده شوند. در غیراینصورت استثنایی صادر شده و برنامه خاتمه می‌یابد.


آشنایی با Synchronization Context

ابتدا یک برنامه‌ی WinForms ساده را آغاز کرده و یک دکمه‌ی جدید را به نام btnGetInfo و یک تکست باکس را به نام txtResults، به آن اضافه کنید. سپس کدهای فرم اصلی آن‌را به نحو ذیل تغییر دهید:
using System;
using System.Linq;
using System.Net;
using System.Windows.Forms;

namespace Async02
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void btnGetInfo_Click(object sender, EventArgs e)
        {
            var req = (HttpWebRequest)WebRequest.Create("http://www.google.com");
            req.Method = "HEAD";
            req.BeginGetResponse(
                asyncResult =>
                {
                    var resp = (HttpWebResponse)req.EndGetResponse(asyncResult);
                    var headersText = formatHeaders(resp.Headers);
                    txtResults.Text = headersText;
                }, null);
        }

        private string formatHeaders(WebHeaderCollection headers)
        {
            var headerString = headers.Keys.Cast<string>()
                                      .Select(header => string.Format("{0}:{1}", header, headers[header]));
            return string.Join(Environment.NewLine, headerString.ToArray());
        }
    }
}
در اینجا از روش دیگری برای دریافت اطلاعات از وب استفاده کرده‌ایم. با استفاده از امکانات HttpWebRequest، کوئری‌های پیشرفته‌تری را می‌توان تهیه کرد. برای مثال می‌توان نوع متد را به HEAD تنظیم نمود؛ تا صرفا مقادیر هدر آدرس درخواستی از سرور، دریافت شوند.
همچنین در این مثال از متد غیرهمزمان BeginGetResponse نیز استفاده شده‌است. در این نوع API خاص، کار با BeginGetResponse آغاز شده و سپس در callback نهایی توسط EndGetResponse، نتیجه‌ی عملیات به دست می‌آید.
اگر برنامه را اجرا کنید، با استثنای زیر مواجه خواهید شد:
 An exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll but was not handled in user code
Additional information: Cross-thread operation not valid: Control 'txtResults' accessed from a thread other than the thread it was created on.
علت اینجا است که asyncResult دریافتی، در تردی دیگر نسبت به ترد اصلی برنامه که UI را اداره می‌کند، اجرا می‌شود. یکی از راه حل‌های این مشکل و انتقال اطلاعات به ترد اصلی برنامه، استفاده از Synchronization Context است:
        private void btnGetInfo_Click(object sender, EventArgs e)
        {
            var sync = SynchronizationContext.Current;
            var req = (HttpWebRequest)WebRequest.Create("http://www.google.com");
            req.Method = "HEAD";
            req.BeginGetResponse(
                asyncResult =>
                {
                    var resp = (HttpWebResponse)req.EndGetResponse(asyncResult);
                    var headersText = formatHeaders(resp.Headers);
                    sync.Post(delegate { txtResults.Text = headersText; }, null);
                }, null);
        }
SynchronizationContext.Current در اینجا چون در ابتدای متد دریافت اطلاعات اجرا می‌شود، به ترد UI، یا ترد اصلی برنامه اشاره می‌کند. به همین جهت این زمینه را نباید داخل Async callback دریافت کرد؛ زیرا ترد جاری آن، ترد UI مدنظر ما نیست. سپس همانطور که ملاحظه می‌کنید، توسط متد Post آن می‌توان اطلاعات را در زمینه‌ی تردی که SynchronizationContext به آن اشاره می‌کند اجرا کرد.


برای درک بهتر آن، سه break point را پیش از متد BeginGetResponse، داخل  Async calback و داخل delegate متد Post قرار دهید. پس از اجرای برنامه، از منوی دیباگ در VS.NET گزینه‌ی Windows و سپس Threads را انتخاب کنید.
در اینجا همانطور که مشخص است، کد داخل delegate تعریف شده، در ترد اصلی برنامه اجرا می‌شود و نه یکی از Worker threadهای ثانویه.
هر چند استفاده از متدهای تو در تو و lambda syntax، نیاز به تعریف چندین متد جداگانه را برطرف کرده‌است، اما باز هم کد ساده‌ای به نظر نمی‌رسد. در سی شارپ 5، برای مدیریت بهتر تمام مشکلات یاد شده، پشتیبانی توکاری از اعمال غیرهمزمان، به هسته‌ی زبان اضافه شده‌است.


Syntax ابتدایی یک متد Async

در ابتدا کلاس و متد Async زیر را در نظر بگیرید:
using System;
using System.Threading.Tasks;

namespace Async01
{
    public class AsyncExample
    {
        public async Task DoWorkAsync(int parameter)
        {
            await Task.Delay(parameter);
            Console.WriteLine(parameter);
        }
    }
}
شیوه‌ی نگارش آن بر اساس راهنمای نوشتن برنامه‌های Async یا Task asynchronous programming model یا به اختصار TAP است:
- در مدل برنامه نویسی TAP، متدهای غیرهمزمان باید یک Task را بازگشت دهند؛ یا نمونه‌ی جنریک آن‌را. البته کامپایلر، async void را نیز پشتیبانی می‌کند ولی در قسمت‌های بعدی بررسی خواهیم کرد که چرا استفاده از آن مشکل‌زا است و باید از آن پرهیز شود.
- همچنین مطابق TAP، اینگونه متدها باید به پسوند Async ختم شوند تا استفاده کننده در حین کار با Intellisense، بتواند آ‌ن‌ها را از متدهای معمولی سریعتر تشخیص دهد.
- از واژه‌ی کلیدی async نیز استفاده می‌گردد تا کامپایلر از وجود اعمال غیر همزمان مطلع گردد.
- await به کامپایلر می‌گوید، عبارت پس از من، یک وظیفه‌ی غیرهمزمان است و ادامه‌ی کدهای نوشته شده، تنها زمانی باید اجرا شوند که عملیات غیرهمزمان معرفی شده، تکمیل گردد.

در متد DoWorkAsync، ابتدا به اندازه‌‌ای مشخص توقف حاصل شده و سپس سطر بعدی یعنی Console.WriteLine اجرا می‌شود.


یک اشتباه عمومی! استفاده از واژه‌های کلیدی async و await متد شما را async نمی‌کنند.

برخلاف تصور ابتدایی از بکارگیری واژه‌های کلیدی async و await، این کلمات نحوه‌ی اجرای متد شما را async نمی‌کنند. این کلمات صرفا برای تشکیل متدهایی که هم اکنون غیرهمزمان هستند، مفید می‌باشند. برای توضیح بیشتر آن به مثال ذیل دقت کنید:
        public async Task<double> GetNumberAsync()
        {
            var generator = new Random();
            await Task.Delay(generator.Next(1000));

            return generator.NextDouble();
        }
در این متد با استفاده از Task.Delay، انجام یک عملیات طولانی شبیه سازی شده‌است؛ مثلا دریافت یک عدد یا نتیجه از یک وب سرویس. سپس در نهایت، عددی را بازگشت داده است. برای بازگشت یک خروجی double، در اینجا از نمونه‌ی جنریک Task استفاده شده‌است.
در ادامه برای استفاده از آن خواهیم داشت:
        public async Task<double> GetSumAsync()
        {
            var leftOperand = await GetNumberAsync();
            var rightOperand = await GetNumberAsync();

            return leftOperand + rightOperand;
        }
خروجی این متد تنها زمانی بازگشت داده می‌شود که نتایج leftOperand و rightOperand از وب سرویس فرضی، دریافت شده باشند و در اختیار مصرف کننده قرارگیرند. بنابراین همانطور که ملاحظه می‌کنید از واژه‌ی کلیدی await جهت تشکیل یک عملیات غیرهمزمان و مدیریت ساده‌تر کدهای نهایی، شبیه به کدهای معمولی همزمان استفاده شده‌است.
در کدهای همزمان متداول، سطر اول ابتدا انجام می‌شود و بعد سطر دوم و الی آخر. با استفاده از واژه‌ی کلیدی await یک چنین عملکردی را با اعمال غیرهمزمان خواهیم داشت. پیش از این برای مدیریت اینگونه اعمال از یک سری callback و یا رخداد استفاده می‌شد. برای مثال ابتدا عملیات همزمانی شروع شده و سپس نتیجه‌ی آن در یک روال رخ‌داد گردان جایی در کدهای برنامه دریافت می‌شد (مانند مثال ابتدای بحث). اکنون تصور کنید که قصد داشتید جمع نهایی حاصل دو عملیات غیرهمزمان را از دو روال رخدادگردان جدا از هم، جمع آوری کرده و بازگشت دهید. هرچند اینکار غیرممکن نیست، اما حاصل کار به طور قطع آنچنان زیبا نبوده و قابلیت نگهداری پایینی دارد. واژه‌ی کلیدی await، انجام اینگونه امور غیرهمزمان را طبیعی و همزمان جلوه می‌دهد. به این ترتیب بهتر می‌توان بر روی منطق و الگوریتم‌های مورد استفاده تمرکز داشت، تا اینکه مدام درگیر مکانیک اعمال غیرهمزمان بود.

امکان استفاده از واژه‌ی کلیدی await در هر جایی از کدها وجود دارد. برای نمونه در مثال زیر، برای ترکیب دو عملیات غیرهمزمان، از await در حین تشکیل عملیات ضرب نهایی، دقیقا در جایی که مقدار متد باید بازگشت داده شود، استفاده شده‌است:
        public async Task<double> GetProductOfSumAsync()
        {
            var leftOperand = GetSumAsync();
            var rightOperand = GetSumAsync();

            return await leftOperand * await rightOperand;
        }
اگر await را از این مثال حذف کنیم، خطای کامپایل زیر را دریافت خواهیم کرد:
 Operator '*' cannot be applied to operands of type 'System.Threading.Tasks.Task<double>' and 'System.Threading.Tasks.Task<double>'
خروجی متد GetSumAsync صرفا یک Task است و نه یک عدد. پس از استفاده از await، عملیات آن انجام شده و بازگشت داده می‌شود.


اگر متد DownloadString همزمان ابتدای بحث را نیز بخواهیم تبدیل به نمونه‌ی async سی‌شارپ 5 کنیم، می‌توان از متد الحاقی جدید آن به نام DownloadStringTaskAsync کمک گرفت:
        public async Task<string> DownloadAsync()
        {
            var webClient = new WebClient();
            return await webClient.DownloadStringTaskAsync("http://www.google.com");
        }
نکته‌ی مهم این کد علاوه بر ساده سازی اعمال غیر همزمان، برای استفاده از نتیجه‌ی نهایی آن، نیازی به SynchronizationContext معرفی شده در تاریخچه‌ی ابتدای بحث نیست. نتیجه‌ی دریافتی از آن در ترد اصلی برنامه تحویل داده شده و به سادگی قابل استفاده است.


سؤال: آیا استفاده از await نیز ترد جاری را قفل می‌کند؟

اگر به کدها دقت کنید، استفاده از await به معنای صبر کردن تا پایان عملیات async است. پس اینطور به نظر می‌رسد که در اینجا نیز ترد اصلی، همانند قبل قفل شده‌است.
        public void TestDownloadAsync()
        {
            Debug.WriteLine("Before DownloadAsync");
            DownloadAsync();
            Debug.WriteLine("After DownloadAsync");
        }
اگر این متد را اجرا کنید (در آن await بکار نرفته)، بلافاصله خروجی ذیل را مشاهده خواهید کرد:
 Before DownloadAsync
After DownloadAsync
به این معنا که در اصل، همانند سایر روش‌های async موجود از دات نت یک، در اینجا نیز فراخوانی متد async ترد اصلی را بلافاصله آزاد می‌کند و ترد آن‌را قفل نخواهد کرد. استفاده از await نیز عملکرد کدها را تغییر نمی‌دهد. تنها کامپایلر در پشت صحنه همان کدهای لازم جهت مدیریت روال‌های رخدادگردان و callbackها را تولید می‌کند، به نحوی که صرفا نحوه‌ی کدنویسی ما همزمان به نظر می‌رسد، اما در پشت صحنه، نحوه‌ی اجرای آن غیرهمزمان است.


برنامه‌های Async و نگارش‌های مختلف دات نت

شاید در ابتدا به نظر برسد که قابلیت‌های جدید async و await صرفا متعلق هستند به دات نت 4.5 به بعد؛ اما خیر. اگر کامپایلری را داشته باشید که از این واژه‌های کلیدی را پشتیبانی کند، امکان استفاده از آن‌ها را با دات نت 4 نیز خواهید داشت. برای این منظور تنها کافی است از VS 2012 به بعد استفاده نمائید. سپس در کنسول پاورشل نیوگت دستور ذیل را اجرا نمائید (فقط برای برنامه‌های دات نت 4 البته):
 PM> Install-Package Microsoft.Bcl.Async
این روال متداول VS.NET بوده است تا به امروز. برای مثال اگر VS 2010 را نصب کنید و سپس یک برنامه‌ی دات نت 3.5 را ایجاد کنید، امکان استفاده‌ی کامل از تمام امکانات سی‌شارپ 4، مانند آرگومان‌های نامدار و یا مقادیر پیش فرض آرگومان‌ها را در یک برنامه‌ی دات نت 3.5 نیز خواهید داشت. همین نکته در مورد async نیز صادق است. VS 2012 (یا نگارش‌های جدیدتر) را نصب کنید و سپس یک پروژه‌ی دات نت 4 را آغاز کنید. امکان استفاده از async و await را خواهید داشت. البته در این حالت دسترسی به متدهای الحاقی جدید را مانند DownloadStringTaskAsync نخواهید داشت. برای رفع این مشکل باید بسته‌ی  Microsoft.Bcl.Async را نیز توسط نیوگت نصب کنید.
مطالب
NHibernate و سطح اول cache آن

این روزها هیچکدام از فناوری‌های دسترسی به داده بدون امکان یکپارچگی آن‌ها با سیستم‌ها و روش‌های متفاوت caching ، مطلوب شمرده نمی‌شوند. ایده اصلی caching هم به زبان ساده به این صورت است :‌ فراهم آوردن روش‌هایی جهت میسر ساختن دسترسی سریعتر به داده‌هایی که به صورت متناوب در برنامه مورد استفاده قرار می‌گیرند، بجای مراجعه مستقیم به بانک اطلاعاتی و خواندن اطلاعات از دیسک سخت.
یکی از تفاوت‌های مهم NHibernate با اکثر ORM های موجود داشتن دو سطح متفاوت cache است : first level cache & second level cache .
برای نمونه Entity framework (در زمان نگارش این مطلب) تنها first level caching را پشتیبانی می‌کند و پروایدر توکار و یکپارچه‌ای را جهت second level caching ارائه نمی‌دهد.
در این قسمت قصد داریم First Level Cache را بررسی کنیم.

سطح اول caching در NHibernate چیست؟

سطح اول caching در تمام ORM هایی که آن‌را پشتیبانی می‌کنند مانند NHibernate ، در طول عمر یک تراکنش تعریف می‌گردد. در این حالت در طی یک تراکنش و طول عمر یک سشن، دریافت اطلاعات هر رکورد از بانک اطلاعاتی، تنها یکبار انجام خواهد شد؛ صرفنظر از اینکه کوئری دریافت اطلاعات آن چندبار فراخوانی می‌‌گردد. یکی از دلایل این روش هم آن است که هیچ دو شیء متفاوتی که هم اکنون در حافظه قرار دارند نباید بیانگر یک رکورد واحد از بانک اطلاعاتی باشند.
در NHibernate به صورت پیش فرض هر زمانیکه از شیء استاندارد session استفاده می‌کنید، سطح اول caching نیز فعال است. درست در زمانیکه سشن خاتمه می‌یابد، این سطح از caching نیز به صورت خودکار تخلیه خواهد گردید.
به first level caching اصطلاحا thought-out cache system یا Cache Through pattern و یا identity map هم گفته می‌شود.

مثال:

روش متداول و استاندارد کار با NHibernate عموما به صورت زیر است:

الف) دریافت شیء Session از Session Factory
ب) شروع یک تراکنش با فراخوانی متد BeginTransaction شیء Session
ج) برای مثال دریافت اطلاعات رکوردی با ID مساوی یک به کمک متد Get مرتبط با شیء Session : این اطلاعات مستقیما از بانک اطلاعاتی دریافت خواهد شد.
د) سپس مجددا سعی در دریافت رکوردی با ID مساوی یک. اینبار اطلاعات این شیء مستقیما از cache خوانده می‌شود و رفت و برگشتی به بانک اطلاعاتی نخواهیم داشت. به همین جهت به این روش identity map هم گفته می‌شود، زیرا NHibernate بر اساس ID منحصربفرد این اشیاء ، identity map خود را تشکیل می‌دهد.
ه) خاتمه‌ی سشن با فراخوانی متد Close آن
بلافاصله
الف) دریافت شیء Session از Session Factory
ب) شروع یک تراکنش با فراخوانی متد BeginTransaction شیء Session
ج) برای مثال دریافت اطلاعات رکوردی با ID مساوی یک به کمک متد Get مرتبط با شیء Session : این اطلاعات مستقیما از بانک اطلاعاتی دریافت خواهد شد (زیرا در یک سشن جدید قرار داریم و همچنین سشن قبلی بسته شده و کش آن تخلیه گشته است).
د) خاتمه‌ی سشن با فراخوانی متد Close آن


سؤال: آیا استفاده از یک سشن سراسری در برنامه صحیح است؟
پاسخ: خیر!
توضیحات: زمانیکه از یک سشن سراسری استفاده می‌کنید، کش NHibernate را در اختیار تمام کاربران همزمان سیستم قرار داده‌اید. در طی یک سشن، همانطور که عنوان شد، بر اساس IDهای اشیاء، یک identity map تشکیل می‌شود و در این حالت به ازای هر رکورد بانک اطلاعاتی فقط و فقط یک شیء در حافظه وجود خواهد داشت که این روش در محیط‌های چندکاربره مانند برنامه‌های وب به زودی تبدیل به نشت اطلاعات و یا تخریب اطلاعات می‌گردد. به همین جهت در این نوع برنامه‌ها روش session-per-request بهترین حالت کاری است.

سؤال: حین به روز رسانی اشیاء جدید، به خطا بر می‌خورم. مشکل در کجاست؟
فرض کنید شیء مفروض Customer را توسط متد session.Get از بانک اطلاعاتی دریافت و تعدادی از خواص آن‌را جهت ساخت شیء جدیدی از کلاس Customer استفاده کرده‌ایم. اکنون اگر بخواهیم این شیء جدید را در بانک اطلاعاتی ذخیره یا به روز رسانی کنیم، NHibernate این اجازه را نمی‌دهد! چرا؟
پاسخ:
خطای متداول این حالت عموما به صورت زیر است:
a different object with the same identifier value was already associated with the session
اگر شخصی با مکانیزم سطح اول caching در NHibernate آشنایی نداشته باشد، شاید ساعاتی را در انجمن‌های مرتبط، جهت یافتن روش حل خطای فوق سپری کند.
همانطور که عنوان شد، در طول یک سشن، نمی‌توان دو شیء با یک ID را به عنوان یک رکورد بانک اطلاعاتی مورد استفاده قرار داد. اولین فراخوانی Get ، سبب کش شدن آن شیء در identity map سطح اول caching می‌گردد.
راه حل:
الف) از چندین و چند شیء استفاده نکنید. هر رکورد باید تنها با یک وهله از شیء‌ایی متناظر باشد.
ب) می‌توان پیش از update‌، کش سطح اول را به صورت دستی خالی کرد. برای این منظور از متد Clear شیء سشن استفاده کنید.
ج) بجای استفاده از متد saveOrUpdate شیء سشن، از متد Merge آن استفاده کنید. به این صورت شیء جدید ایجاد شده با شیء موجود در کش یکی خواهد شد.
د) می‌توان بجای تخلیه کل کش (حالت ب)، کش مرتبط با شیء Customer را به صورت دستی خالی کرد. برای این منظور از متد Evict شیء سشن استفاده نمائید.

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

سؤال: آیا می‌توان سطح اول caching را غیرفعال کرد؟
پاسخ:بله.
توضیحات:
عموما کلیه ORMs جهت Batching یا Bulk data operations (برای مثال ثبت تعداد زیادی رکورد یا به روز رسانی تعداد بالایی از آن‌ها، یا نمایش فقط خواندنی تعداد زیادی رکورد و گزارشگیری از آن‌ها) کارآیی مطلوبی ندارند. نمونه‌ای از آن‌را در مبحث جاری ملاحظه کرده‌اید. هر شیءایی که به نحوی به سشن جاری وارد می‌شود تحت نظر قرار می‌گیرد و این مورد در تعداد بالای ثبت یا به روز رسانی رکوردها، یعنی کاهش سرعت و کارآیی، به علاوه مصرف بالای حافظه. به همین جهت باید به خاطر داشت که ORMs جهت سناریوهای OLTP مناسب هستند و کسانی که سرعت و کارآیی ORMs را با Batch processing اندازه گیری می‌کنند، کلا درکی از فلسفه‌ی وجودی ORMs و ساختار درونی آن‌ها ندارند!
خوشبختانه NHibernate با معرفی Stateless Sessions بر این مشکل فائق آمده است. در اینجا بجای ISession تنها کافی است از IStatelessSession استفاده گردد:
using (IStatelessSession statelessSession = sessionFactory.OpenStatelessSession())
using (ITransaction transaction = statelessSession.BeginTransaction())
{
//now insert 1,000,000 records!
}
در این حالت سیستم دو مزیت عمده را تجربه خواهد کرد: سرعت بالای ثبت اطلاعات با تعداد زیاد رکورد و همچنین مصرف پایین حافظه از آنجائیکه یک IStatelessSession ارجاعی را به اشیایی که بارگذاری می‌کند، در خود نگهداری نخواهد کرد.
تنها باید به خاطر داشت که در این حالت lazy loading پشتیبانی نمی‌شود و همچنین رخدادهای درونی NHibernate نیز لغو خواهند شد.

مطالب
آشنایی با NHibernate - قسمت چهارم

در این قسمت یک مثال ساده از insert ، load و delete را بر اساس اطلاعات قسمت‌های قبل با هم مرور خواهیم کرد. برای سادگی کار از یک برنامه Console استفاده خواهد شد (هر چند مرسوم شده است که برای نوشتن آزمایشات از آزمون‌های واحد بجای این نوع پروژه‌ها استفاده شود). همچنین فرض هم بر این است که database schema برنامه را مطابق قسمت قبل در اس کیوال سرور ایجاد کرده اید (نکته آخر بحث قسمت سوم).

یک پروژه جدید از نوع کنسول را به solution برنامه (همان NHSample1 که در قسمت‌های قبل ایجاد شد)، اضافه نمائید.
سپس ارجاعاتی را به اسمبلی‌های زیر به آن اضافه کنید:
FluentNHibernate.dll
NHibernate.dll
NHibernate.ByteCode.Castle.dll
NHSample1.dll : در قسمت‌های قبل تعاریف موجودیت‌ها و نگاشت‌ آن‌ها را در این پروژه class library ایجاد کرده بودیم و اکنون قصد استفاده از آن را داریم.

اگر دیتابیس قسمت قبل را هنوز ایجاد نکرده‌اید، کلاس CDb را به برنامه افزوده و سپس متد CreateDb آن‌را به برنامه اضافه نمائید.

using FluentNHibernate;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHSample1.Mappings;

namespace ConsoleTestApplication
{
class CDb
{
public static void CreateDb(IPersistenceConfigurer dbType)
{
var cfg = Fluently.Configure().Database(dbType);

PersistenceModel pm = new PersistenceModel();
pm.AddMappingsFromAssembly(typeof(CustomerMapping).Assembly);
var sessionSource = new SessionSource(
cfg.BuildConfiguration().Properties,
pm);

var session = sessionSource.CreateSession();
sessionSource.BuildSchema(session, true);
}
}
}
اکنون برای ایجاد دیتابیس اس کیوال سرور بر اساس نگاشت‌های قسمت قبل، تنها کافی است دستور ذیل را صادر کنیم:

CDb.CreateDb(
MsSqlConfiguration
.MsSql2008
.ConnectionString("Data Source=(local);Initial Catalog=HelloNHibernate;Integrated Security = true")
.ShowSql());

تمامی جداول و ارتباطات مرتبط در دیتابیسی که در کانکشن استرینگ فوق ذکر شده است، ایجاد خواهد شد.

در ادامه یک کلاس جدید به نام Config را به برنامه کنسول ایجاد شده اضافه کنید:

using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate;
using NHSample1.Mappings;

namespace ConsoleTestApplication
{
class Config
{
public static ISessionFactory CreateSessionFactory(IPersistenceConfigurer dbType)
{
return
Fluently.Configure().Database(dbType
).Mappings(m => m.FluentMappings.AddFromAssembly(typeof(CustomerMapping).Assembly))
.BuildSessionFactory();
}
}
}
اگر بحث را دنبال کرده باشید، این کلاس را پیشتر در کلاس FixtureBase آزمون واحد خود، به نحوی دیگر دیده بودیم. برای کار با NHibernate‌ نیاز به یک سشن مپ شده به موجودیت‌های برنامه می‌باشد که توسط متد CreateSessionFactory کلاس فوق ایجاد خواهد شد. این متد را به این جهت استاتیک تعریف کرده‌ایم که هیچ نوع وابستگی به کلاس جاری خود ندارد. در آن نوع دیتابیس مورد استفاده ( برای مثال اس کیوال سرور 2008 یا هر مورد دیگری که مایل بودید)، به همراه اسمبلی حاوی اطلاعات نگاشت‌های برنامه معرفی شده‌اند.

اکنون سورس کامل مثال برنامه را در نظر بگیرید:

کلاس CDbOperations جهت اعمال ثبت و حذف اطلاعات:

using System;
using NHibernate;
using NHSample1.Domain;

namespace ConsoleTestApplication
{
class CDbOperations
{
ISessionFactory _factory;

public CDbOperations(ISessionFactory factory)
{
_factory = factory;
}

public int AddNewCustomer()
{
using (ISession session = _factory.OpenSession())
{
using (ITransaction transaction = session.BeginTransaction())
{
Customer vahid = new Customer()
{
FirstName = "Vahid",
LastName = "Nasiri",
AddressLine1 = "Addr1",
AddressLine2 = "Addr2",
PostalCode = "1234",
City = "Tehran",
CountryCode = "IR"
};

Console.WriteLine("Saving a customer...");

session.Save(vahid);
session.Flush();//چندین عملیات با هم و بعد

transaction.Commit();

return vahid.Id;
}
}
}

public void DeleteCustomer(int id)
{
using (ISession session = _factory.OpenSession())
{
using (ITransaction transaction = session.BeginTransaction())
{
Customer customer = session.Load<Customer>(id);
Console.WriteLine("Id:{0}, Name: {1}", customer.Id, customer.FirstName);

Console.WriteLine("Deleting a customer...");
session.Delete(customer);

session.Flush();//چندین عملیات با هم و بعد

transaction.Commit();
}
}
}
}
}
و سپس استفاده از آن در برنامه

using System;
using FluentNHibernate.Cfg.Db;
using NHibernate;
using NHSample1.Domain;

namespace ConsoleTestApplication
{
class Program
{
static void Main(string[] args)
{
//CDb.CreateDb(SQLiteConfiguration.Standard.ConnectionString("data source=sample.sqlite").ShowSql());
//return;

//todo: Read ConnectionString from app.config or web.config
using (ISessionFactory session = Config.CreateSessionFactory(
MsSqlConfiguration
.MsSql2008
.ConnectionString("Data Source=(local);Initial Catalog=HelloNHibernate;Integrated Security = true")
.ShowSql()
))
{
CDbOperations db = new CDbOperations(session);
int id = db.AddNewCustomer();
Console.WriteLine("Loading a customer and delete it...");
db.DeleteCustomer(id);
}

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}
توضیحات:
نیاز است تا ISessionFactory را برای ساخت سشن‌های دسترسی به دیتابیس ذکر شده در تنظمیات آن جهت استفاده در تمام تردهای برنامه، ایجاد نمائیم. لازم به ذکر است که تا قبل از فراخوانی BuildSessionFactory این تنظیمات باید معرفی شده باشند و پس از آن دیگر اثری نخواهند داشت.
ایجاد شیء ISessionFactory هزینه بر است و گاهی بر اساس تعداد کلاس‌هایی که باید مپ شوند، ممکن است تا چند ثانیه به طول انجامد. به همین جهت نیاز است تا یکبار ایجاد شده و بارها مورد استفاده قرار گیرد. در برنامه به کرات از using استفاده شده تا اشیاء IDisposable را به صورت خودکار و حتمی، معدوم نماید.

بررسی متد AddNewCustomer :
در ابتدا یک سشن را از ISessionFactory موجود درخواست می‌کنیم. سپس یکی از بهترین تمرین‌های کاری جهت کار با دیتابیس‌ها ایجاد یک تراکنش جدید است تا اگر در حین اجرای کوئری‌ها مشکلی در سیستم، سخت افزار و غیره پدید آمد، دیتابیسی ناهماهنگ حاصل نشود. زمانیکه از تراکنش استفاده شود، تا هنگامیکه دستور transaction.Commit آن با موفقیت به پایان نرسیده باشد، اطلاعاتی در دیتابیس تغییر نخواهد کرد و از این لحاظ استفاده از تراکنش‌ها جزو الزامات یک برنامه اصولی است.
در ادامه یک وهله از شیء Customer را ایجاد کرده و آن‌را مقدار دهی می‌کنیم (این شیء در قسمت‌های قبل ایجاد گردید). سپس با استفاده از session.Save دستور ثبت را صادر کرده، اما تا زمانیکه transaction.Commit فراخوانی و به پایان نرسیده باشد، اطلاعاتی در دیتابیس ثبت نخواهد شد.
نیازی به ذکر سطر فلاش در این مثال نبود و NHibernate اینکار را به صورت خودکار انجام می‌دهد و فقط از این جهت عنوان گردید که اگر چندین عملیات را با هم معرفی کردید، استفاده از session.Flush سبب خواهد شد که رفت و برگشت‌ها به دیتابیس حداقل شود و فقط یکبار صورت گیرد.
در پایان این متد، Id ثبت شده در دیتابیس بازگشت داده می‌شود.

چون در متد CreateSessionFactory ، متد ShowSql را نیز ذکر کرده بودیم، هنگام اجرای برنامه، عبارات SQL ایی که در پشت صحنه توسط NHibernate تولید می‌شوند را نیز می‌توان مشاهده نمود:



بررسی متد DeleteCustomer :
ایجاد سشن و آغاز تراکنش آن همانند متد AddNewCustomer است. سپس در این سشن، یک شیء از نوع Customer با Id ایی مشخص load‌ خواهد گردید. برای نمونه، نام این مشتری نیز در کنسول نمایش داده می‌شود. سپس این شیء مشخص و بارگذاری شده را به متد session.Delete ارسال کرده و پس از فراخوانی transaction.Commit ، این مشتری از دیتابیس حذف می‌شود.

برای نمونه خروجی SQL پشت صحنه این عملیات که توسط NHibernate مدیریت می‌شود، به صورت زیر است:

Saving a customer...
NHibernate: select next_hi from hibernate_unique_key with (updlock, rowlock)
NHibernate: update hibernate_unique_key set next_hi = @p0 where next_hi = @p1;@p0 = 17, @p1 = 16
NHibernate: INSERT INTO [Customer] (FirstName, LastName, AddressLine1, AddressLine2, PostalCode, City, CountryCode, Id) VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7);@p0 = 'Vahid', @p1 = 'Nasiri', @p2 = 'Addr1', @p3 = 'Addr2', @p4 = '1234', @p5 = 'Tehran', @p6 = 'IR', @p7 = 16016
Loading a customer and delete it...
NHibernate: SELECT customer0_.Id as Id2_0_, customer0_.FirstName as FirstName2_0_, customer0_.LastName as LastName2_0_, customer0_.AddressLine1 as AddressL4_2_0_, customer0_.AddressLine2 as AddressL5_2_0_, customer0_.PostalCode as PostalCode2_0_, customer0_.City as City2_0_, customer0_.CountryCode as CountryC8_2_0_ FROM [Customer] customer0_ WHERE customer0_.Id=@p0;@p0 = 16016
Id:16016, Name: Vahid
Deleting a customer...
NHibernate: DELETE FROM [Customer] WHERE Id = @p0;@p0 = 16016
Press a key...
استفاده از دیتابیس SQLite بجای SQL Server در مثال فوق:

فرض کنید از هفته آینده قرار شده است که نسخه سبک و تک کاربره‌ای از برنامه ما تهیه شود. بدیهی است SQL server برای این منظور انتخاب مناسبی نیست (هزینه بالا برای یک مشتری، مشکلات نصب، مشکلات نگهداری و امثال آن برای یک کاربر نهایی و نه یک سازمان بزرگ که حتما ادمینی برای این مسایل در نظر گرفته می‌شود).
اکنون چه باید کرد؟ باید برنامه را از صفر بازنویسی کرد یا قسمت دسترسی به داده‌های آن‌را کلا مورد باز بینی قرار داد؟ اگر برنامه اسپاگتی ما اصلا لایه دسترسی به داده‌ها را نداشت چه؟! همه جای برنامه پر است از SqlCommand و Open و Close ! و عملا استفاده از یک دیتابیس دیگر یعنی باز نویسی کل برنامه.
همانطور که ملاحظه می‌کنید، زمانیکه با NHibernate کار شود، مدیریت لایه دسترسی به داده‌ها به این فریم ورک محول می‌شود و اکنون برای استفاده از دیتابیس SQLite تنها باید تغییرات زیر صورت گیرد:
ابتدا ارجاعی را به اسمبلی System.Data.SQLite.dll اضافه نمائید (تمام این اسمبلی‌های ذکر شده به همراه مجموعه FluentNHibernate ارائه می‌شوند). سپس:
الف) ایجاد یک دیتابیس خام بر اساس کلاس‌های domain و mapping تعریف شده در قسمت‌های قبل به صورت خودکار

CDb.CreateDb(SQLiteConfiguration.Standard.ConnectionString("data source=sample.sqlite").ShowSql());
ب) تغییر آرگومان متد CreateSessionFactory

//todo: Read ConnectionString from app.config or web.config
using (ISessionFactory session = Config.CreateSessionFactory(
SQLiteConfiguration.Standard.ConnectionString("data source=sample.sqlite").ShowSql()
))
{
...

نمایی از دیتابیس SQLite تشکیل شده پس از اجرای متد قسمت الف ، در برنامه Lita :




دریافت سورس برنامه تا این قسمت

نکته:
در سه قسمت قبل، تمام خواص پابلیک کلاس‌های پوشه domain را به صورت معمولی و متداول معرفی کردیم. اگر نیاز به lazy loading در برنامه وجود داشت، باید تمامی کلاس‌ها را ویرایش کرده و واژه کلیدی virtual را به کلیه خواص پابلیک آن‌ها اضافه کرد. علت هم این است که برای عملیات lazy loading ، فریم ورک NHibernate باید یک سری پروکسی را به صورت خودکار جهت کلاس‌های برنامه ایجاد نماید و برای این امر نیاز است تا بتواند این خواص را تحریف (override) کند. به همین جهت باید آن‌ها را به صورت virtual تعریف کرد. همچنین تمام سطرهای Not.LazyLoad نیز باید حذف شوند.

ادامه دارد ...


مطالب
بررسی کلمات کلیدی Const و ReadOnly در سی شارپ
تعریف: Constant فیلدی است که مقدار آن در زمان کامپایل (Compile time) مشخص می‌شود و این مقدار هیچگاه نمی‌تواند تغییر کند (ثابت است). از کلمه کلیدی (Keyword) ، const برای تعریف یک constant استفاده می‌شود.

  تعاریف اولیه :
Constant Field : فیلد ثابتی که مستقیما در یک Class و یا Struct تعریف می‌شود.
Constant Local : ثابتی که در بلاک‌های برنامه (بدنه یک تابع ، حلقه تکرار و ...) تعریف می‌شود.

همه‌ی انواع درون ساخت (Built in) در زبان #C مانند (انواع عددی، بولین، کاراکتر، رشته و نوع‌های شمارشی) و اشاره‌گرهای تهی (null reference) می‌توانند بصورت constant تعریف شوند. باید توجه داشت که عبارت تعریف و مقدار دهی یک constant (ثابت) باید بصورتی باشد که در زمان کامپایل کاملا قابل ارزیابی باشد.

جدول مقایسه‌ای بین Const و ReadOnly
Constant
ReadOnly
میتواند به Field‌ها و همچنین local‌‌ها اعمال شود. تنها به Field ها  اعمال می‌شود. 
مقدار دهی اولیه آن الزامی است. 
مقدار دهی اولیه می‌تواند هنگام تعریف و یا در درون سازنده انجام شود (در هیچ متد دیگری امکان پذیر نیست). 
 تخصیص حافظه انجام نمی‌شود و مقدار آن در کد‌های IL گنجانده می‌شود (توضیح در ادامه مطلب).   تخصیص حافظه بصورت داینامیک انجام می‌شود و می‌توانیم در زمان اجرا مقدار آن را بدست آوریم. 
 ثابت‌ها در #C بصورت پیش فرض از نوع static هستند. بدین معنا که از طریق نام کلاس  قابل دسترسی هستند.   تنها از طریق وهله سازی از یک کلاس قابل دسترسی هستند. 
 نوع‌های درون ساز (built in) و Null Reference ها  را می‌توان بصورت const تعریف کرد.
Boolean,Char, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double, Decimal , string. 
مشابه Constant ها
مقدار آن در طول عمر یک برنامه ثابت است.
 مقدار آن می‌تواند در هنگام فراخوانی سازنده برای وهله‌های مختلف متفاوت باشد.
فیلد‌های const را نمی‌توان بصورت پارامتر‌های out و ref استفاده کرد.
فیلد‌های ReadOnly را می‌توان بصورت پارامتر‌های ref و out در درون سازنده استفاده کرد. 

نحوه تعریف یک constant :





همانطور که در تصویر مشاهده می‌کنید در کنار نماد انتخابی برای const‌ها یک قفل کوچک (نشان از غیرقابل تغییر بودن) قرار گرفته است .


مثالی از تعریف و رفتار Constant‌ها در #C :
const int field_constant = 10;  //constant field
static void Main(string[] args)
{
    const int x = 10, y = 15;   //constant local :correct
    const int z = x + y;        //constant local : correct;
    const int a = x + GetVariableValue();//Error
}
public static int GetVariableValue()
{
    const int localx = 10;
    return 10;
}
در خطوط اول و دوم ارزش متغیر‌های x,y,z بدرستی محاسبه و ارزیابی شده‌است. اما در خط سوم تخصیص مقدار برای ثابت a به زمان اجرای برنامه موکول شده است. در نتیجه با بروز خطا مواجه می‌شویم .

فیلد‌های فقط خواندنی ReadOnly


در #C فقط Field‌‌ها را می‌توان بصورت ReadOnly  تعریف کرد. این فیلد‌ها یا در زمان تعریف و یا از طریق سازنده مقدار دهی می‌شوند.






بررسی تفاوت readonly و  const در سطح IL

برای مشاهده کدهای سطح میانی (IL Code) از ابزار خط فرمان Developer Command ویژوال استدیو 2017 و همچنین برنامه ILdasm استفاده شده است. همانطور که در جدول مقایسه‌ای بیان شد، برای constant field ها  تخصیص حافظه‌ای صورت نمی‌گیرد و مقادیر مستقیما در کد‌های IL گنجانده می‌شود.
مثال: 
 class Program
    {
        public const int numberOfDays = 7;
        public readonly double piValue = 3.14;

        static void Main(string[] args)
        {
            
        }
    }












اگر فایل Exe کد فوق را توسط نرم افزار IL Dasm مشاهده کنید، خواهید دید که مقدار ذخیره شده در numberOfDays در کد IL گنجانده شده است : 








ولی مقدار ذخیره شده در piValue در زمان اجرا قابل دسترسی می‌باشد.






مشکل Versioning فیلدهای const
public const int numberOfDays = 7;
public readonly double piValue = 3.14;
اگر کد‌های فوق را به یک اسمبلی مجزا منتقل کنیم و از این کد‌ها در پروژه‌ای جدید استفاده کنیم، وضعیت Code ‌های IL به صورت زیر است:
کد برنامه اصلی که ارجاعی به اسمبلی جانبی دارد:
static void Main(string[] args)
{
   var readEx = new MyLib.TestClass();
   var readConstValue = MyLib.TestClass.numberOfDays;
   var readReadOnlyValue = readEx.piValue;
}
کد‌های IL :
.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size 17 (0x11)
  .maxstack  1
  .locals init ([0] class [MyLib]MyLib.TestClass readEx,
  [1] int32 readConstValue,
  [2] float64 readOnlyValue)
  IL_0000:  nop
  IL_0001:  newobj   instance void [MyLib]MyLib.TestClass::.ctor()
  IL_0006:  stloc.0 //readEx
  IL_0007:  ldc.i4.7  //ارزش ذخیره شده در کد
  IL_0008:  stloc.1 //readConstValue
  IL_0009:  ldloc.0 //readEg
  IL_000a:  ldfld float64 [MyLib]MyLib.TestClass::piValue
  IL_000f:  stloc.2 //readReadOnlyValue
  IL_0010:  ret
} // end of method Program::Main
همانطور که می‌بینید ارزش ذخیره شده در کد IL، همان ارزشی است که در اسمبلی مجزا ذخیره شده است.
اگر در کتابخانه جانبی ارزش فیلد const را تغییر دهید و آن را مجدد کامپایل کنید، تا زمانیکه اسمبلی برنامه اصلی را کامپایل نکرده‌اید، همان ارزش قبلی در برنامه نمایش داده می‌شود.
برای غلبه بر این مشکل از فیلد‌های Static ReadOnly استفاده می‌کنیم.

مثال:
public class ReadonlyStatic
{
  public static readonly string x = "Hi";
  public static readonly string y;
  public ReadonlyStatic()
  {
     //y = "Hello"; This is wrong
  }
  static ReadonlyStatic()
  {
    y = "Hello";
  }
}

اولین مشکلی که با استفاده از فیلد‌های Static ReadOnly حل می‌شود، مشکل  Versioning فیلد‌های Const است. بدین ترتیب دیگر نیازی به کامپایل مجدد برنامه مصرف کننده نیست .
نکته بعدی که در کد فوق نشان داده شده‌است، فیلد‌های static readOnly در زمان تعریف و یا تنها از طریق سازنده‌ی static می‌توانند مقدار دهی شوند.

مقایسه ReadOnly و Static  :

ReadOnly
 Static
 هم در زمان تعریف و هم از طریق سازنده می‌توان آن را مقدار دهی کرد.   در زمان تعریف و تنها از طریق سازنده static می‌توان آن را مقدار دهی کرد.
مقدار بر اساس مقادیری که در سازنده‌ها تعیین می‌شود متفاوت است.
 مقادیر بعد از مقدار دهی اولیه تغییر نمی‌کنند. 


چه زمانی از Const و چه زمانی از ReadOnly استفاده کنیم :

  • زمانی باید از Const استفاده کرد که مطمئن هستیم ارزش ذخیره شده در آن در طول عمر یک برنامه تغییر نمی‌کند. بطور مثال ذخیره تعداد روز هفته در یک فیلد از نوع Constant. اگر شک داریم که ممکن است این ارزش تغییر کند، می‌توانیم از حالت static readOnly برای غلبه بر مشکل Versioning استفاده کنیم.
  • از آنجائیکه مقادیر constant در کد‌های IL گنجانده می‌شوند، برای رسیدن به کارآیی بهتر، مقادیری را که در طول عمر یک برنامه تغییر نمی‌کنند، به صورت  const تعریف می‌کنیم.
  • هر زمان تصمیم داشتیم Constant هایی به ازای هر وهله از کلاس داشته باشیم از ReadOnly استفاده می‌کنیم. 
 
مطالب
معرفی Decorator Design Pattern

فرض کنید که می‌خواهیم یک برنامه برای یک فروشگاه نوشیدنی (مانند coffee shop) بنویسیم ، این فروشگاه در ابتدای کار ممکن است ، منوی ساده‌ای جهت ارائه به مشتری داشته باشد. برای مثال ممکن است که فقط 3 یا 4 محصول داشته باشد. بنابراین ممکن است ما برنامه‌ای را که می‌خواهیم برای این مشتری بنویسیم به صورت زیر طراحی کنیم:

 


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

بنابراین تعداد این حالات را در نظر بگیرید که در آینده ممکن است چقدر زیاد بشود:

 

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

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

خوب ما به این مسئله به این صورت نگاه می‌کنیم که شروع می‌کنیم با نوشیدنی‌ها و آن‌ها را با چاشنی‌ها در زمان اجرا تزیین (Decorate) می‌کنیم؛ نه کامپایل.

برای مثال اگر مشتری ما یک نوشیدنی DarkRoast با Mocha و Whip خواست، سپس ما :
1- یک شی از DarkRoast  ایجاد می‌کنیم .
2- آن را با یک شی از  Mocha  تزئین می‌کنیم.
3- آن را با یک شی از Whip - تزیین می‌کنیم.
4- متد Cost()  را صدا می‌زنیم و  یک Delegation  را برای اضافه کردن قیمت چاشنی‌ها در نظر می‌گیریم.

بسیار خوب؛ اما ما عملیات تزئین یک شی را چگونه انجام می‌دهیم و delegation ما چگونه عمل می‌کند .
یک اشاره : به شیء تزئین کننده، مانند یک Wrappers فکر کنید. اجاز بدهید ببینم که چه طور این کار را انجام می‌دهیم.
1- یک شی از DarkRoast  ایجاد می‌کنیم.
 
 

2- آن را با یک شی از  Mocha  تزئین می‌کنیم.

3- آن را با یک شی از Whip تزیین می‌کنیم 


 4- حالا زمان محاسبه قیمت محصول برای مشتری فرا رسیده است. ما این کار را را با صدا زدن بیرونی‌ترین Decorator(Whip)  انجام می‌دهیم و شی  whip به کمک  Delegate مابقی توابع cost را صدا می‌زند.



  در آخر شما می‌توانید پیاده سازی این برنامه را به زبان جاوا در زیر مشاهد نمایید.

public abstract class Beverage
{
     string description ="unknow beverage";
     public String getDescription(){
         return description;
     }
     public abstract double cost();
}

public abstract class CondimentDecorator extends Beverage {
    public abstract string getDescription();
}

public class Espersso extends Beverage{
   public Espersso()
   {
      description="Espersso";
   }
   public double cost(){
     return 1.99;
   }
}

public class HouseBelend extends Beverage {
   public HouseBelend()
   {
     description="HouseBelend";
   }
   public double cost()
   {
     return .89;
   }
}

public class mocha extends condimateDecorator {
  Beverage beverage;
  public mocha(Beverage beverage)
  {
     this.beverage=beverage;
  }
   public string getDescription(){
      return beverage.getdescription() + "Mocha";
   }
   public double cost(){
     return .20 +beverage.cost
   }
}


// Now Use These classes in Final form
Beverage beverage=new Espersso();
//Customers want a coffe with double milk and whip
beverage=new mocha(beverage);
beverage=new mocha(meverage);
beverage=new whip(beverage);

system.out.println(beverage.getDescription() + "$" +beverage.cost());
مطالب
آشنایی با WPF قسمت ششم : DataContext بخش سوم
در قسمت قبلی با مبدل‌ها آشنا شدیم و با استفاده از این ویژگی، دو کنترل Radio Button و CheckBox را بایند کردیم. الان تنها دو کنترل مانده تا آن‌ها را متصل کنیم؛ کنترل ListBox و تقویم، که در این قسمت لیست را بررسی می‌کنیم.

ListBox
در مورد لیست، ما قبلا نام کشورها را با استفاده از تگ ListBoxItem به طور دستی اضافه می‌کردیم و هر گونه ویرایش و اضافه کردن عکس و دیگر اشیاء را داخل این تگ برای هر آیتم جداگانه انجام می‌دادیم؛ مثل تصویر زیر که هر آیتم شامل یک تگ تصویر و دو تگ TextBlock است که یکی از آن‌ها رنگی شده است. کد هر آیتم به طور جداگانه و دستی اضافه شده است.


 ولی در روش بایندینگ چنین چیزی ممکن نیست و تنها با استفاده از یک Template موارد بالا را ایجاد می‌کنیم. پس محتویات سابق ListBox را حذف کرده و تگهای زیر را جهت افزودن یک قالب داده Data Template به شیء لیست اضافه می‌کنیم. حال اگر داده‌های لیست شده خود را روانه  DataContext کنید باید این اطلاعات نمایش داده شوند.
 <ListBox Grid.Row="3" Name="MyListBox" Grid.Column="1" Margin="10"  Height="80" >
               <ListBox.ItemTemplate>
                    <DataTemplate>
                        <WrapPanel>
                            <Image Width="24" Height="24" Source="{Binding Flag}"></Image>
                            <TextBlock Padding="5 5 0 0" Text="{Binding Name}"></TextBlock>
                        </WrapPanel>
                    </DataTemplate>
               </ListBox.ItemTemplate>
            </ListBox>
در برنامه ما مشکلی که هست، کد بالا جهت اتصال به DataContext ای است که قبلا پر شده است (DataContext کل View اصلی یا والد تمامی اشیاء مشتق از آن). حتما به یاد دارید که ما این شیء را با مدل یک رکورد ذخیره شده (مدل Person) در منبع داده‌ها پر کرده بودیم. پس استفاده از این روش در حال حاضر منتفی است. ممکن است شما در طول ساخت یک پنجره چندین و چند جا نیاز به منابع داده مختلفی داشته باشید ولی عموما DataContext با یک مدل جهت نمایش یا ذخیره یک رکورد بایند شده است. پس چکار کنیم؟

ارائه این نکته ضروری است که همه اشیاء خصوصیت DataContext را دارند و ما در مثال قبلی DataContext ریشه یا والد اشیاء را پر کردیم. اگر مقاله "ساختار سلسله مراتبی " را به یاد بیاورید، گفتیم که هر شیء در صورتیکه خصوصیت وابسته‌ای برایش تعریف نشده باشد، به سمت اشیاء والد حرکت می‌کند، به این جهت بود که همه‌ی کنترل‌ها به منبع داده‌ها دسترسی داشتند. پس ما اگر DataContext لیست را پر کنیم، لیست دلیلی برای دسترسی به DataContext اشیاء والد ندارد و خصوصیت پر شده‌ی خودش را در نظر می‌گیرد. پس بیایید این مورد را امتحان کنیم:
من کلاس زیر را جهت ارسال لیستی از کشورها به همراه آدرس پرچمشان، بر می‌گردانم:
دلیل استفاده از کلاس ObservableCollection در کد زیر به جای استفاده از اشیایی چون Ilist و ... این بود که این کلاس به اینترفیس هایی چون INotifyPropertyChanged مزین گشته و هر گونه تغییری در این مجموعه، از قبیل حذف و اضافه را اطلاع رسانی کرده و مدل تغییر یافته را به سمت ویو هدایت می‌کند.
using System.Collections.ObjectModel;

namespace test
{
    public class Country
    {
        public string Flag {
            get { return "Images/flags/" + Name + ".png"; }
        }
        public string Name { get; set; }

        public int Id { get; set; }

        public ObservableCollection<Country> GetCountries()
        {
            var countries = new ObservableCollection<Country>();
            countries.Add(new Country(){Id =1,Name = "Afghanistan"});
            countries.Add(new Country() { Id = 2, Name = "Albania" });
            countries.Add(new Country() { Id = 3, Name = "Angola" });

            countries.Add(new Country() { Id = 4, Name = "Bahrain" });
            countries.Add(new Country() { Id = 5, Name = "Bermuda" });
            countries.Add(new Country() { Id =6, Name = "Iran" });

            return countries;
        }
    }
}
برنامه را اجرا کرده و انتظار داریم که بتوانیم لیست پر شده‌ای از داده‌ها را ببینیم؛ ولی در کمال تعجب لیست خالی است. خطایی هم برگردانده نمی‌شود.

دلیل این مشکل این است که DataContext برای نمایش یک Object تهیه شده است و در مورد داده‌های لیستی باید از خصوصیتی به نام ItemsSource استفاده کرد که برای داده‌های لیستی IEnumerables، بهینه شده است.
پس به این ترتیب می‌نویسیم :
   public MainWindow()
        {
            InitializeComponent();
            person = Person.GetPerson();
            DataContext = person;

            //خط جدید
            MyListBox.ItemsSource = new Country().GetCountries();
        }
حال برنامه را اجرا کرده تا نتیجه را مشاهده کنید.

شکل‌های زیر یک نمودار از ارتباط با Object برای واکشی داده هاست:

شکل زیر همان نمودار بالا را ترسیم میکند ولی دیگر از مبدل پیش فرض WPF خبری نیست و مبدل اختصاصی به اسم ColorBrush جایگزین آن شده است:

نمودار زیر هم دسترسی به مجموعه ای از داده‌های لیستی است که از طریق ItemsSource خوانده می‌شوند:

کد زیر همچنین برای اتصال به کار می‌رود:
        public MainWindow()
        {
            InitializeComponent();
            person = Person.GetPerson();
            DataContext = person;

            //خط جدید
            MyListBox.DataContext = new Country().GetCountries();
            MyListBox.SetBinding(ItemsControl.ItemsSourceProperty, new Binding());
        }
روش بالا اتصال را برقرار می‌کند ولی من توصیه چندانی در استفاده از آن نمی‌کنم. آزاد گذاشتن DataContext یک لیست، یک مزیت هم دارد و آن این است که خارج از تگ Item‌ها یعنی همان تگ لیست، موقعی که  از بایندینگ استفاده می‌کنید، در واقع از DataContext کمک گرفته می‌شود؛ چون خود ListBox یک آیتم نیست که بخواهد با آیتمی در یک لیست سر و کله بزند. بلکه می‌تواند به راحتی به یک شیء، خود را بایند کند؛ مثال زیر نمونه‌ای از آن است.

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

حال که توانستیم لیست را پر کنیم باید کشوری را که در رکورد واکشی شده آمده است، در لیست انتخاب کنیم.
توجه داشته باشید که باید لیست را از طریق خصوصیت ItemsSource پر کرده باشید و DataContext را دستکاری نکرده باشید.
خصوصیت Country در کلاس Person می‌تواند به دو صورت زیر باشد:
 public int Country { get; set; }
 public Country Country { get; set; }

که در هر دو حال از خصوصیت SelectedValue شی ListBox استفاده می‌شود. هر دو خط زیر به ترتیب برای استفاده از مقادیر بالا به کار می‌روند:
<ListBox Grid.Row="3" Name="MyListBox" Grid.Column="1" Margin="10"  Height="80" SelectedValuePath="Id" SelectedValue="{Binding Country}"  >               
<ListBox Grid.Row="3" Name="MyListBox" Grid.Column="1" Margin="10"  Height="80" SelectedValuePath="Id" SelectedValue="{Binding Country.Id}"  >
خصوصیت SelectedValuePath برای مشخص کردن اینکه کدام فیلد را باید در آیتم‌های لیست، جست و جو کند به کار می‌رود که ما در اینجا فیلد Id را که در کلاس Country قرار دارد، معرفی کرده‌ایم.
خصوصیت‌های دیگر یک شیء لیستی چون ListBox و ComboBox و ... SelectedIndex است که اندیس یک آیتم انتخابی را بازگردانده یا جهت انتخاب یک آیتم، اندیس آن را دریافت می‌کند. SelectedItem و SelectedItems هم شیء یا شیء‌هایی از مدل را (در اینجا Country) که در لیست انتخاب شده‌اند، بر می‌گرداند (فقط خواندنی).
 نتیجه اینکه اگر روش بالا با دستکاری DataContext انجام می‌گرفت دیگر استفاده از فیلد Country در مدل Peron ممکن نبود.
مطالب
نوشتن آزمون‌های واحد به کمک کتابخانه‌ی Moq - قسمت دوم - تنظیم مقادیر بازگشتی متدها
در قسمت قبل با مفاهیمی مانند fakes ،stubs ،dummies و mocks آشنا شدیم و در اولین آزمایشی که نوشتیم، کار تدارک dummies را به عنوان پارامترهای سازنده‌ی سرویس مورد بررسی، توسط کتابخانه‌ی Moq و اشیاء <Mock<T آن انجام دادیم؛ پارامترهایی که ذکر آن‌ها ضروری بودند، اما در آزمایش ما مورد استفاده قرار نمی‌گرفتند. در این قسمت می‌خواهیم کار تدارک stubs را توسط کتابخانه‌ی Moq انجام دهیم؛ به عبارتی می‌خواهیم مقادیر بازگشتی از متدهای اشیاء Mock شده را تنظیم و کنترل کنیم.


تنظیم خروجی متدهای اشیاء Mock شده

در انتهای قسمت قبل، آزمون واحد متد Accept، با شکست مواجه شد؛ چون متد Validate استفاده شده، همواره مقدار false را بر می‌گرداند:
_identityVerifier.Initialize();
var isValidIdentity = _identityVerifier.Validate(
     application.Applicant.Name, application.Applicant.Age, application.Applicant.Address);

در ادامه شیء Mock از نوع IIdentityVerifier را طوری تنظیم خواهیم کرد که بر اساس یک applicant مشخص، خروجی true را بازگشت دهد:
namespace Loans.Tests
{
    [TestClass]
    public class LoanApplicationProcessorShould
    {
        [TestMethod]
        public void Accept()
        {
            var product = new LoanProduct {Id = 99, ProductName = "Loan", InterestRate = 5.25m};
            var amount = new LoanAmount {CurrencyCode = "Rial", Principal = 2_000_000_0};
            var applicant =
                new Applicant {Id = 1, Name = "User 1", Age = 25, Address = "This place", Salary = 1_500_000_0};
            var application = new LoanApplication {Id = 42, Product = product, Amount = amount, Applicant = applicant};

            var mockIdentityVerifier = new Mock<IIdentityVerifier>();
            mockIdentityVerifier.Setup(x => x.Validate(applicant.Name, applicant.Age, applicant.Address))
                .Returns(true);

            var mockCreditScorer = new Mock<ICreditScorer>();

            var processor = new LoanApplicationProcessor(mockIdentityVerifier.Object, mockCreditScorer.Object);
            processor.Process(application);

            Assert.IsTrue(application.IsAccepted);
        }
    }
}
در اینجا ابتدا کار با شیء Mock شده آغاز می‌شود. سپس باز ذکر متد Setup، می‌توان به صورت strongly typed به تمام متدهای اینترفیس IIdentityVerifier دسترسی یافت و آن‌ها را تنظیم کرد. تا اینجا متد مدنظر را از اینترفیس IIdentityVerifier انتخاب کردیم. سپس توسط متد Returns، خروجی دقیقی را برای آن مشخص می‌کنیم.
به این ترتیب زمانیکه در متد Process کلاس LoanApplicationProcessor کار به بررسی هویت کاربر می‌رسد، اگر متد Validate آن با اطلاعات applicant مشخصی که تنظیم کردیم، یکی بود، متغیر isValidIdentity که حاصل بررسی identityVerifier.Validate_ است، به true مقدار دهی خواهد شد. برای بررسی آن یک break-point را در این نقطه قرار داده و آزمون واحد را در حالت دیباگ اجرا کنید.
البته هرچند اگر اکنون نیز این آزمایش واحد را مجددا بررسی کنیم، باز هم با شکست مواجه خواهد شد؛ چون مرحله‌ی بعدی بررسی، کار با سرویس ICreditScorer است که هنوز تنظیم نشده‌است:
_creditScorer.CalculateScore(application.Applicant.Name, application.Applicant.Address);
if (_creditScorer.Score < MinimumCreditScore)
{
    return application.IsAccepted;
}
فعلا این قسمت از code را comment می‌کنیم تا آزمایش واحد ما با موفقیت به پایان برسد. در قسمت بعدی کار تنظیم مقادیر خواص را انجام داده و این قسمت از code را نیز پوشش خواهیم داد.


تطابق با آرگومان‌های متدها در متدهای Mock شده

با تنظیمی که انجام دادیم، اگر متد Validate به مشخصات شیء applicant مشخص ما برسد، خروجی true را بازگشت می‌دهد. برای مثال اگر در این بین تنها نام شخص تغییر کند، خروجی بازگشت داده شده همان false خواهد بود. اما اگر این نام برای ما اهمیتی نداشت و قصد داشتیم با تمام نام‌های متفاوتی که دریافت می‌کند، بازهم خروجی true را بازگشت دهد، می‌توان از قابلیت argument matching کتابخانه‌ی Moq و کلاس It آن استفاده کرد:
var mockIdentityVerifier = new Mock<IIdentityVerifier>();
mockIdentityVerifier.Setup(x => x.Validate(
        //applicant.Name,
        It.IsAny<string>(),
        applicant.Age, 
        applicant.Address))
    .Returns(true);
()<It.IsAny<string در اینجا به این معنا است که هر نوع ورودی رشته‌ای، قابل قبول بوده و دیگر متد Validate بر اساس یک نام مشخص، مورد بررسی قرار نمی‌گیرد. IsAny یک متد جنریک است و بر اساس نوع آرگومان مدنظر که برای مثال در اینجا رشته‌ای است، نوع جنریک آن مشخص می‌شود.
بدیهی است در این حالت باید سایر پارامترها دقیقا با مقادیر مشخص شده تطابق داشته باشند و اگر این موارد نیز اهمیتی نداشتند، می‌توان به صورت زیر عمل کرد:
var mockIdentityVerifier = new Mock<IIdentityVerifier>();
mockIdentityVerifier.Setup(x => x.Validate(
        //applicant.Name,
        It.IsAny<string>(),
        //applicant.Age,
        It.IsAny<int>(),
        //applicant.Address
        It.IsAny<string>()
        ))
    .Returns(true);
در این حالت متد Validate، صرفنظر از ورودهای آن، همواره مقدار true را باز می‌گرداند.
البته این نوع تنظیمات بیشتر برای حالات غیرمشخص مانند استفادهاز Guidها به عنوان پارامترها و مقادیر، می‌تواند مفید باشد.


تقلید متدهایی که پارامترهایی از نوع out دارند

اگر به اینترفیس IIdentityVerifier که در قسمت قبل معرفی شد دقت کنیم، یکی از متدهای آن دارای خروجی از نوع out است:
using Loans.Models;

namespace Loans.Services.Contracts
{
    public interface IIdentityVerifier
    {
        void Validate(string applicantName, int applicantAge, string applicantAddress, out bool isValid);
// ...
    }
}
این متد خروجی ندارد، اما خروجی اصلی آن از طریق پارامتر isValid، دریافت می‌شود. برای استفاده‌ی از آن، متد Process کلاس LoanApplicationProcessor را به صورت زیر تغییر می‌دهیم:
//var isValidIdentity = _identityVerifier.Validate(
//    application.Applicant.Name, application.Applicant.Age, application.Applicant.Address);
_identityVerifier.Validate(
    application.Applicant.Name, application.Applicant.Age, application.Applicant.Address,
    out var isValidIdentity);
در این حالت اگر آزمون واحد متد Accept را بررسی کنیم، با شکست مواجه خواهد شد. به همین جهت تنظیمات Mocking این متد را به صورت زیر تعریف می‌کنیم:
var isValidOutValue = true;
mockIdentityVerifier.Setup(x => x.Validate(applicant.Name,
    applicant.Age,
    applicant.Address,
    out isValidOutValue));
برای تنظیم متدهایی که پارامترهایی از نوع out دارند، باید ابتدا مقدار مورد انتظار را مشخص کرد. بنابراین مقدار آن‌را به true در اینجا تنظیم کرده‌ایم. سپس در متد Setup، متدی تنظیم شده‌است که پارامتری از نوع out دارد. در آخر نیازی به ذکر متد Returns نیست؛ چون خروجی متد از نوع void است.
اکنون اگر مجددا آزمون واحد متد Accept را اجرا کنیم، با موفقیت به پایان می‌رسد.


تقلید متدهایی که پارامترهایی از نوع ref دارند

اگر به اینترفیس IIdentityVerifier که در قسمت قبل معرفی شد دقت کنیم، یکی از متدهای آن دارای خروجی از نوع ref است:
using Loans.Models;

namespace Loans.Services.Contracts
{
    public interface IIdentityVerifier
    {        
          void Validate(string applicantName, int applicantAge, string applicantAddress,
                             ref IdentityVerificationStatus status);
// ...
    }
}
این متد خروجی ندارد، اما خروجی اصلی آن از طریق پارامتر status، دریافت می‌شود و نوع آن به صورت زیر تعریف شده‌است تا وضعیت تعیین هویت شخص را مشخص کند:
namespace Loans.Models
{
    public class IdentityVerificationStatus
    {
        public bool Passed { get; set; }
    }
}
 برای استفاده‌ی از آن، متد Process کلاس LoanApplicationProcessor را به صورت زیر تغییر می‌دهیم تا بتوان به نمونه‌ی وهله سازی شده‌ی status دسترسی یافت:
IdentityVerificationStatus status = null;
  _identityVerifier.Validate(
      application.Applicant.Name, application.Applicant.Age, application.Applicant.Address,
      ref status);

if (!status.Passed)
{
    return application.IsAccepted;
}
در این حالت اگر آزمون واحد متد Accept را بررسی کنیم، با شکست مواجه خواهد شد. به همین جهت تنظیمات Mocking این متد را به صورت زیر تعریف می‌کنیم که با متدهای out دار مقداری متفاوت است:
ابتدا در سطح کلاس آزمایش واحد یک delegate را تعریف می‌کنیم:
delegate void ValidateCallback(string applicantName,
    int applicantAge,
    string applicantAddress,
    ref IdentityVerificationStatus status);
این delegate دقیقا دارای همان پارامترهای متد Validate در حال بررسی است.
اکنون روش استفاده‌ی از آن برای برپایی تنظیمات mocking متد Validate از نوع ref دار به صورت زیر است:
mockIdentityVerifier
    .Setup(x => x.Validate(applicant.Name,
        applicant.Age,
        applicant.Address,
        ref It.Ref<IdentityVerificationStatus>.IsAny))
    .Callback(new ValidateCallback(
        (string applicantName,
         int applicantAge,
         string applicantAddress,
         ref IdentityVerificationStatus status) =>
            status = new IdentityVerificationStatus {Passed = true}));
تنظیمات قسمت Setup آن آشنا است؛ بجز قسمت ref آن که از It.Ref<IdentityVerificationStatus>.IsAny استفاده کرده‌است. چون نوع پارامتر، ref است، باید از It.Ref استفاده کرد که به نوع بازگشت داده شده‌ی IdentityVerificationStatus اشاره می‌کند. IsAny آن هم هر نوع ورودی از این دست را می‌پذیرد.
سپس متد جدید Callback را مشاهده می‌کنید. توسط آن می‌توان یک قطعه کد سفارشی را زمانیکه متد Mock شده‌ی Validate ما اجرا می‌شود، اجرا کرد. در اینجا delegate سفارشی ما اجرا شده و مقدار status را بر می‌گرداند؛ اما در ادامه این مقدار را به یک new IdentityVerificationStatus سفارشی تنظیم می‌کنیم که در آن مقدار خاصیت Passed، مساوی true است.
اکنون اگر مجددا آزمون واحد متد Accept را اجرا کنیم، با موفقیت به پایان می‌رسد.


تنظیم متدهای Mock شده جهت بازگشت null

فرض کنید اینترفیسی به صورت زیر تعریف شده‌است:
namespace Loans.Services.Contracts
{
    public interface INullExample
    {
        string SomeMethod();
    }
}
و اگر بخواهیم برای آن آزمون واحدی را بنویسیم که خروجی این متد به صورت مشخصی نال باشد، می‌توان تنظیمات Moq آن‌را به صورت زیر انجام داد:
namespace Loans.Tests
{
    [TestClass]
    public class LoanApplicationProcessorShould
    {        
        [TestMethod]
        public void NullReturnExample()
        {
            var mock = new Mock<INullExample>();

            mock.Setup(x => x.SomeMethod());
            //.Returns<string>(null);

            string mockReturnValue = mock.Object.SomeMethod();

            Assert.IsNull(mockReturnValue);
        }
    }
}
در اینجا دو روش را برای بازگشت نال ملاحظه می‌کنید:
الف) می‌توان همانند سابق متد Returns را ذکر کرد که نال بر می‌گرداند؛ اما با این تفاوت که حتما باید نوع آرگومان جنریک آن‌را نیز بر اساس خروجی متد، مشخص کرد.
ب) کتابخانه‌ی Moq، مقدار خروجی پیش‌فرض تمام متدهایی را که یک نوع ارجاعی را باز می‌گردانند، نال درنظر می‌گیرد و عملا نیازی به ذکر متد Returns در اینجا نیست.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: MoqSeries-02.zip