مطالب
انجام پی در پی اعمال Async به کمک Iterators - قسمت اول

تقریبا تمام اعمال کار با شبکه در Silverlight از مدل asynchronous programming پیروی می‌کنند؛ از فراخوانی یک متد وب سرویس تا دریافت اطلاعات از وب و غیره. اگر در سایر فناوری‌های موجود در دات نت فریم ورک برای مثال جهت کار با یک وب سرویس هر دو متد همزمان و غیرهمزمان در اختیار برنامه نویس هستند اما اینجا خیر. اینجا فقط روش‌های غیرهمزمان مرسوم هستند و بس. خیلی هم خوب. یک چارچوب کاری خوب باید روش استفاده‌ی صحیح از کتابخانه‌های موجود را نیز ترویج کند و این مورد حداقل در Silverlight اتفاق افتاده است.
برای مثال فراخوانی‌های زیر را در نظر بگیرید:
private int n1, n2;

private void FirstCall()
{
Service.GetRandomNumber(10, SecondCall);
}

private void SecondCall(int number)
{
n1 = number;
Service.GetRandomNumber(n1, ThirdCall);
}

private void ThirdCall(int number)
{
n2 = number;
// etc
}
عموما در اعمال Async پس از پایان عملیات در تردی دیگر، یک متد فراخوانی می‌گردد که به آن callback delegate نیز گفته می‌شود. برای مثال توسط این سه متد قصد داریم اطلاعاتی را از یک وب سرویس دریافت و استفاده کنیم. ابتدا FirstCall فراخوانی می‌شود. پس از پایان کار آن به صورت خودکار متد SecondCall فراخوانی شده و این متد نیز یک عملیات Async دیگر را شروع کرده و الی آخر. در نهایت قصد داریم توسط مقادیر بازگشت داده شده منطق خاصی را پیاده سازی کنیم. همانطور که مشاهده می‌کنید این اعمال زیبا نیستند! چقدر خوب می‌شد مانند دوران synchronous programming (!) فراخوانی‌های این متدها به صورت ذیل انجام می‌شد:
private void FetchNumbers()
{
int n1 = Service.GetRandomNumber(10);
int n2 = Service.GetRandomNumber(n1);
}
در برنامه نویسی متداول همیشه عادت داریم که اعمال به صورت A –> B –> C انجام شوند. اما در Async programming ممکن است ابتدا C انجام شود، سپس A و بعد B یا هر حالت دیگری صرفنظر از تقدم و تاخر آن‌ها در حین معرفی متدهای مرتبط در یک قطعه کد. همچنین میزان خوانایی این نوع کدنویسی نیز مطلوب نیست. مانند مثال اول ذکر شده، یک عملیات به ظاهر ساده به چندین متد منقطع تقسیم شده است. البته به کمک lambda expressions مثال اول را به شکل زیر نیز می‌توان در طی یک متد ارائه داد اما اگر تعداد فراخوانی‌ها بیشتر بود چطور؟ همچنین آیا استفاده از عدد n2 بلافاصله پس از عبارت ذکر شده مجاز است؟ آیا عملیات واقعا به پایان رسیده و مقدار مطلوب به آن انتساب داده شده است؟
private void FetchNumbers()
{
int n1, n2;

Service.GetRandomNumber(10, result =>
{
n1 = result;
Service.GetRandomNumber(n1, secondResult =>
{
n2 = secondResult;
});
});
}

به عبارتی می‌خواهیم کل اعمال انجام شده در متد FetchNumbers هنوز Async باشند (ترد اصلی برنامه را قفل نکنند) اما پی در پی انجام شوند تا مدیریت آن‌ها ساده‌تر شوند (هر لحظه دقیقا بدانیم که کجا هستیم) و همچنین کدهای تولیدی نیز خواناتر باشند.
روش استانداری که توسط الگوهای برنامه نویسی برای حل این مساله پیشنهاد می‌شود، استفاده از الگوی coroutines است. توسط این الگو می‌توان چندین متد Async را در حالت معلق قرار داده و سپس در هر زمانی که نیاز به آن‌ها بود عملیات آن‌ها را از سر گرفت.
دات نت فریم ورک حالت ویژه‌ای از coroutines را توسط Iterators پشتیبانی می‌کند (از C# 2.0 به بعد) که در ابتدا نیاز است از دیدگاه این مساله مروری بر آن‌ها داشته باشیم. مثال بعد یک enumerator را به همراه yield return ارائه داده است:

using System;
using System.Collections.Generic;
using System.Threading;

namespace CoroutinesSample
{
class Program
{
static void printAll()
{
foreach (int x in integerList())
{
Console.WriteLine(x);
}
}

static IEnumerable<int> integerList()
{
yield return 1;
Thread.Sleep(1000);
yield return 2;
yield return 3;
}

static void Main()
{
printAll();
}
}
}

کامپایلر سی شارپ در عمل یک state machine را برای پیاده سازی این عملیات به صورت خودکار تولید خواهد کرد:

private bool MoveNext()
{
switch (this.<>1__state)
{
case 0:
this.<>1__state = -1;
this.<>2__current = 1;
this.<>1__state = 1;
return true;

case 1:
this.<>1__state = -1;
Thread.Sleep(0x3e8);
this.<>2__current = 2;
this.<>1__state = 2;
return true;

case 2:
this.<>1__state = -1;
this.<>2__current = 3;
this.<>1__state = 3;
return true;

case 3:
this.<>1__state = -1;
break;
}
return false;
}

در حین استفاده از یک IEnumerator ابتدا در وضعیت شیء Current آن قرار خواهیم داشت و تا زمانیکه متد MoveNext آن فراخوانی نشود هیچ اتفاق دیگری رخ نخواهد داد. هر بار که متد MoveNext این enumerator فرخوانی گردد (برای مثال توسط یک حلقه‌ی foreach) اجرای متد integerList ادامه خواهد یافت تا به yield return بعدی برسیم (سایر اعمال تعریف شده در حالت تعلیق قرار دارند) و همینطور الی آخر.
از همین قابلیت جهت مدیریت اعمال Async پی در پی نیز می‌توان استفاده کرد. State machine فوق تا پایان اولین عملیات تعریف شده صبر می‌کند تا به yield return برسد. سپس با فراخوانی متد MoveNext به عملیات بعدی رهنمون خواهیم شد. به این صورت دیدگاهی پی در پی از یک سلسه عملیات غیرهمزمان حاصل می‌گردد.

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


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

مطالب
Lambda Syntax و کارآیی
در این مطلب می‌خواهیم کارآیی event handlers پیاده سازی شده با روش‌های متفاوتی را مورد بررسی قراردهیم.
به مثال زیر توجه کنید:
class EventSource : System.Progress<int>
{
    public async System.Threading.Tasks.Task<int> PerformExpensiveCalculation()
    {
        var sum = 0;
        for (var i = 0; i < 100; i++)
        {
            await System.Threading.Tasks.Task.Delay(100);
            sum += i;
            this.OnReport(sum);
        }
        return sum;
    }
}

static class Program
{
    static void Main(string[] args)
    {
        var source = new EventSource();
        System.EventHandler<int> handler = (_, progress) => System.Console.WriteLine(progress);
        source.ProgressChanged += handler;
        System.Console.WriteLine(source.PerformExpensiveCalculation().Result);
        source.ProgressChanged -= handler;

        source.ProgressChanged += ProgressChangedMethod;
        System.Console.WriteLine(source.PerformExpensiveCalculation().Result);
        source.ProgressChanged -= ProgressChangedMethod;
    }

    private static void ProgressChangedMethod( object sender, int e )
    {
        System.Console.WriteLine(e);
    }
}
در مثال بالا دو نسخه‌ی مختلف از event handler را با دو روش، (روش اول) با استفاده از Lambda syntax و (روش دوم) با استفاده از یک متد، به صورت جدا تعریف شده، پیاده سازی کرده‌ایم.
خوب؛ برای اندازه گیری کارآیی این دو روش باید کمی فکر کنیم که چه چیزی کارآیی این دو روش را تغییر می‌دهد؟
آیا پردازش event با اضافه کردن و حذف کردن event handler؟ و یا پردازش درون event باعث تغییر در کارآیی می‌شود؟
این، سوال مهمی در تست کارآیی این دو روش مختلف است. اگر پردازش درون event باعث ایجاد تفاوت کارآیی می‌شود، با استفاده از این برنامه می‌توان آن را اندازه گیری کرد. با این حال اگر تفاوت کارآیی با اضافه کردن و حذف کردن event handler اتفاق می‌افتد، با این برنامه بعید است بتوان این روش را تست کرد چرا که فقط یکبار این عمل انجام می‌شود.
قبل از شروع به اندازه گیری کارآیی این دو روش‌، اجازه بدهید ابتدا به کد IL آن‌ها نگاهی کنیم. (روش اول با استفاده از Lambda syntax)
IL_0007:  ldsfld     class [mscorlib]System.EventHandler`1<int32> LambdaPerformance.Program/'<>c'::'<>9__0_0'
IL_000c:  dup
IL_000d:  brtrue.s   IL_0026
IL_000f:  pop
IL_0010:  ldsfld     class LambdaPerformance.Program/'<>c' LambdaPerformance.Program/'<>c'::'<>9'
IL_0015:  ldftn      instance void LambdaPerformance.Program/'<>c'::'<Main>b__0_0'(object, int32)
IL_001b:  newobj     instance void class [mscorlib]System.EventHandler`1<int32>::.ctor(object, native int)
IL_0020:  dup
IL_0021:  stsfld     class [mscorlib]System.EventHandler`1<int32> LambdaPerformance.Program/'<>c'::'<>9__0_0'
IL_0026:  stloc.1
IL_0027:  ldloc.0
IL_0028:  ldloc.1
IL_0029:  callvirt   instance void class [mscorlib]System.Progress`1<int32>::add_ProgressChanged(class [mscorlib]System.EventHandler`1<!0>)
IL_002e:  nop
IL_002f:  ldloc.0
IL_0030:  callvirt   instance class [mscorlib]System.Threading.Tasks.Task`1<int32> LambdaPerformance.EventSource::PerformExpensiveCalculation()
IL_0035:  callvirt   instance !0 class [mscorlib]System.Threading.Tasks.Task`1<int32>::get_Result()
IL_003a:  call       void [mscorlib]System.Console::WriteLine(int32)
IL_003f:  nop
IL_0040:  ldloc.0
IL_0041:  ldloc.1
IL_0042:  callvirt   instance void class [mscorlib]System.Progress`1<int32>::remove_ProgressChanged(class [mscorlib]System.EventHandler`1<!0>)
در بالا 5 دستورالعمل برای اضافه کردن event handler وجود دارد (از IL_0010 تا IL_0029) و یک دستور برای حذف handler وجود دارد (IL_0042).
قبل از شروع مقایسه، کد IL روش دوم را نیز بررسی می‌کنیم:
IL_004a:  ldftn      void LambdaPerformance.Program::ProgressChangedMethod(object, int32)
IL_0050:  newobj     instance void class [mscorlib]System.EventHandler`1<int32>::.ctor(object, native int)
IL_0055:  callvirt   instance void class [mscorlib]System.Progress`1<int32>::add_ProgressChanged(class [mscorlib]System.EventHandler`1<!0>)
IL_005a:  nop
IL_005b:  ldloc.0
IL_005c:  callvirt   instance class [mscorlib]System.Threading.Tasks.Task`1<int32> LambdaPerformance.EventSource::PerformExpensiveCalculation()
IL_0061:  callvirt   instance !0 class [mscorlib]System.Threading.Tasks.Task`1<int32>::get_Result()
IL_0066:  call       void [mscorlib]System.Console::WriteLine(int32)
IL_006b:  nop
IL_006c:  ldloc.0
IL_006d:  ldnull
IL_006e:  ldftn      void LambdaPerformance.Program::ProgressChangedMethod(object, int32)
IL_0074:  newobj     instance void class [mscorlib]System.EventHandler`1<int32>::.ctor(object, native int)
IL_0079:  callvirt   instance void class [mscorlib]System.Progress`1<int32>::remove_ProgressChanged(class [mscorlib]System.EventHandler`1<!0>)
همانطور که مشاهده می‌کنید در روش دوم برای اضافه کردن event handler تنها 3 خط وجود دارند (IL_004a تا IL_0055) و برای حذف کردن آن نیز 3 خط وجود دارند (IL_006e تا IL_0079).

برای اندازه گیری دقیق، برنامه‌ی بالا را کمی تغییر می‌دهیم. ما میزان اضافه و حذف شدن event handler را می‌خواهیم اندازه‌گیری کنیم و کاری به زمان اجرای یک عملیات نداریم. بنابراین فراخوانی ()PerformExpensiveCalculation را comment کرده و به صورت خیلی ساده فقط handler را اضافه و حذف می‌کنیم.
static class Program
{
    static void Main(string[] args)
    {
        for (var repeats = 10; repeats <= 1000000; repeats *= 10)
        {
            VersionOne(repeats);
            VersionTwo(repeats);
        }
    }

    private static void VersionOne(int repeats)
    {
        var timer = new System.Diagnostics.Stopwatch();
        timer.Start();

        var source = new EventSource();
        for (var i = 0; i < repeats; i++)
        {
            System.EventHandler<int> handler = (_, progress) => System.Console.WriteLine(progress);
            source.ProgressChanged += handler;
            // Console.WriteLine(source.PerformExpensiveCalculation().Result);
            source.ProgressChanged -= handler;
        }

        timer.Stop();

        System.Console.WriteLine($"Version one: {repeats} add/remove takes {timer.ElapsedMilliseconds}ms");
    }

    private static void VersionTwo(int repeats)
    {
        var timer = new System.Diagnostics.Stopwatch();
        timer.Start();

        var source = new EventSource();
        for (var i = 0; i < repeats; i++)
        {
            source.ProgressChanged += ProgressChangedMethod;
            // Console.WriteLine(source.PerformExpensiveCalculation().Result);
            source.ProgressChanged -= ProgressChangedMethod;
        }

        timer.Stop();

        System.Console.WriteLine($"Version two: {repeats} add/remove takes {timer.ElapsedMilliseconds}ms");
    }

    private static void ProgressChangedMethod(object sender, int e)
    {
        System.Console.WriteLine(e);
    }
}
و چنین خروجی را تولید می‌کند (البته نسبت به سرعت CPU این زمان‌ها متفاوت خواهد بود)
Version one: 10 add/remove takes 0ms
Version two: 10 add/remove takes 0ms
Version one: 100 add/remove takes 0ms
Version two: 100 add/remove takes 0ms
Version one: 1000 add/remove takes 0ms
Version two: 1000 add/remove takes 0ms
Version one: 10000 add/remove takes 0ms
Version two: 10000 add/remove takes 1ms
Version one: 100000 add/remove takes 8ms
Version two: 100000 add/remove takes 13ms
Version one: 1000000 add/remove takes 93ms
Version two: 1000000 add/remove takes 121ms
خوب؛ اگر در یک اجرای برنامه، شما یک میلیون بار event handler را اضافه و حذف کنید، 28ms می‌توانید صرفه جویی کنید (در روش اول).

توجه:
اگر در برنامه‌ی شما یک میلیون بار event handler اضافه و حذف می‌شوند، نیاز به بازنگری مجدد در طراحی کلی برنامه تان دارد.

یک اشتباه بزرگ

با ایجاد یک تغییر در روش اول (Lambda syntax)، ممکن است تاثیر بسیار زیادی را در عملکرد برنامه مشاهده کنید:
private static void VersionOne(int repeats)
{
    var timer = new System.Diagnostics.Stopwatch();
    timer.Start();

    var source = new EventSource();
    for (var i = 0; i < repeats; i++)
    {
        // System.EventHandler<int> handler = (_, progress) => System.Console.WriteLine(progress);
        source.ProgressChanged += (_, progress) => System.Console.WriteLine(progress);
        // Console.WriteLine(source.PerformExpensiveCalculation().Result);
        source.ProgressChanged -= (_, progress) => System.Console.WriteLine(progress);
    }

    timer.Stop();

    System.Console.WriteLine($"Version one: {repeats} add/remove takes {timer.ElapsedMilliseconds}ms");
}
به جای تعریف یک متغیر محلی برای عبارت Lambda، دستور اضافه و حذف کردن event handler را به صورت inline استفاده کردیم. خروجی این روش به صورت زیر می‌شود:
Version one: 10 add/remove takes 0ms
Version two: 10 add/remove takes 0ms
Version one: 100 add/remove takes 1ms
Version two: 100 add/remove takes 0ms
Version one: 1000 add/remove takes 102ms
Version two: 1000 add/remove takes 0ms
Version one: 10000 add/remove takes 10509ms
Version two: 10000 add/remove takes 1ms
Version one: 100000 add/remove takes 1039014ms
Version two: 100000 add/remove takes 11ms
همانطور که مشاهده می‌کنید، روش اول خیلی خیلی آهسته است. توجه کنید من بعد از یکصد هزار بار اضافه و حذف کردن handler، به دلیل طولانی شدن، عملیات را قطع کردم. خب دلیل این همه کندی چیست؟ بیایید نگاهی به کد IL درون حلقه‌ی روش اول بیاندازیم.
  IL_0018:  nop
  IL_0019:  ldloc.1
  IL_001a:  ldsfld     class [mscorlib]System.EventHandler`1<int32> LambdaPerformance.Program/'<>c'::'<>9__1_0'
  IL_001f:  dup
  IL_0020:  brtrue.s   IL_0039
  IL_0022:  pop
  IL_0023:  ldsfld     class LambdaPerformance.Program/'<>c' LambdaPerformance.Program/'<>c'::'<>9'
  IL_0028:  ldftn      instance void LambdaPerformance.Program/'<>c'::'<VersionOne>b__1_0'(object, int32)
  IL_002e:  newobj     instance void class [mscorlib]System.EventHandler`1<int32>::.ctor(object, native int)
  IL_0033:  dup
  IL_0034:  stsfld     class [mscorlib]System.EventHandler`1<int32> LambdaPerformance.Program/'<>c'::'<>9__1_0'
  IL_0039:  callvirt   instance void class [mscorlib]System.Progress`1<int32>::add_ProgressChanged(class [mscorlib]System.EventHandler`1<!0>)
  IL_003e:  nop
  IL_003f:  ldloc.1
  IL_0040:  ldsfld     class [mscorlib]System.EventHandler`1<int32> LambdaPerformance.Program/'<>c'::'<>9__1_1'
  IL_0045:  dup
  IL_0046:  brtrue.s   IL_005f
  IL_0048:  pop
  IL_0049:  ldsfld     class LambdaPerformance.Program/'<>c' LambdaPerformance.Program/'<>c'::'<>9'
  IL_004e:  ldftn      instance void LambdaPerformance.Program/'<>c'::'<VersionOne>b__1_1'(object, int32)
  IL_0054:  newobj     instance void class [mscorlib]System.EventHandler`1<int32>::.ctor(object, native int)
  IL_0059:  dup
  IL_005a:  stsfld     class [mscorlib]System.EventHandler`1<int32> LambdaPerformance.Program/'<>c'::'<>9__1_1'
  IL_005f:  callvirt   instance void class [mscorlib]System.Progress`1<int32>::remove_ProgressChanged(class [mscorlib]System.EventHandler`1<!0>)
  IL_0064:  nop
  IL_0065:  nop
  IL_0066:  ldloc.2
  IL_0067:  stloc.3
  IL_0068:  ldloc.3
  IL_0069:  ldc.i4.1
  IL_006a:  add
  IL_006b:  stloc.2
  IL_006c:  ldloc.2
  IL_006d:  ldarg.0
  IL_006e:  clt
  IL_0070:  stloc.s    V_4
  IL_0072:  ldloc.s    V_4
  IL_0074:  brtrue.s   IL_0018
به خطهای ( IL_0028 و IL_0034 و IL_004e و IL_005a ) در کد بالا دقت کنید. توجه داشته باشید که event handler اضافه شده با event handler حذف شده، با هم متفاوت هستند. حذف کردن event handler ای که به جایی متصل نیست باعث ایجاد خطا نمی‌شود ولی کاری هم انجام نمی‌دهد. بنابراین اتفاقی که در روش اول درون حلقه می‌افتد این است که بیش از یک میلیون بار event handler به delegate اضافه می‌شود. همه‌ی آنها یکسان هستند؛ اما همچنان CPU و حافظه مصرف می‌کنند.

ممکن است شما به این نتیجه رسیده باشید که استفاده از Lambda syntax برای اضافه و حذف کردن event handler آهسته‌تر از، استفاده از متد جدا است، این یک اشتباه بزرگ است. در صورتی که شما اضافه و حذف کردن event handler را با استفاده از Lambda syntax به شکل صحیح انجام ندهید، به سرعت، در معیارهای کارآیی خود را نشان می‌دهد.

دانلود برنامه بالا
مطالب
معرفی Reactive extensions
Reactive extensions یا به صورت خلاصه Rx ،کتابخانه‌ی سورس باز تهیه شده‌ای توسط مایکروسافت است که اگر بخواهیم آن‌را به ساده‌ترین شکل ممکن تعریف کنیم، معنای Linq to events را می‌دهد و امکان مدیریت تعامل‌های پیچیده‌ی async را به صورت declaratively فراهم می‌کند. هدف آن بسط فضای نام System.Linq و تبدیل نتایج یک کوئری LINQ به یک مجموعه‌ی Observable است؛ به همراه مدیریت مسایل همزمانی آن.
این افزونه جزو موفق‌ترین کتابخانه‌های دات نتی مایکروسافت در سال‌های اخیر به شما می‌رود؛ تا حدی که معادل‌های بسیاری از آن برای زبان‌های دیگر مانند Java، JavaScript، Python، ‍CPP و غیره نیز تهیه شده‌اند.


استفاده از Rx به همراه یک کوئری LINQ

یک برنامه‌ی کنسول جدید را ایجاد کنید. سپس برای نصب کتابخانه‌ی Rx، دستور ذیل را در کنسول پاورشل نیوگت اجرا نمائید:
 PM> Install-Package Rx-Main
نصب آن از طریق نیوگت، به صورت خودکار کلیه وابستگی‌های مرتبط با آن‌را نیز به پروژه‌ی جاری اضافه می‌کند:
<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Rx-Core" version="2.2.4" targetFramework="net45" />
  <package id="Rx-Interfaces" version="2.2.4" targetFramework="net45" />
  <package id="Rx-Linq" version="2.2.4" targetFramework="net45" />
  <package id="Rx-Main" version="2.2.4" targetFramework="net45" />
  <package id="Rx-PlatformServices" version="2.2.4" targetFramework="net45" />
</packages>
سپس متد Main این برنامه را به نحو ذیل تغییر دهید:
using System;
using System.Linq;

namespace Rx01
{
    class Program
    {
        static void Main(string[] args)
        {
            var query = Enumerable.Range(1, 5).Select(number => number);
            foreach (var number in query)
            {
                Console.WriteLine(number);
            }
            finished();
        }

        private static void finished()
        {
            Console.WriteLine("Done!");
        }
    }
}
در اینجا یک سری عملیات متداول را مشاهده می‌کنید. بازه‌ای از اعداد توسط متد Enumerable.Range ایجاد شده و سپس به کمک یک حلقه‌، تمام آیتم‌های آن نمایش داده می‌شوند. همچنین در پایان کار نیز یک متد دیگر فراخوانی شده‌است.
اکنون اگر بخواهیم همین عملیات را توسط Rx انجام دهیم، به شکل زیر خواهد بود:
using System;
using System.Linq;
using System.Reactive.Linq;

namespace Rx01
{
    class Program
    {
        static void Main(string[] args)
        {
            var query = Enumerable.Range(1, 5).Select(number => number);
            var observableQuery = query.ToObservable();
            observableQuery.Subscribe(onNext: number => Console.WriteLine(number), onCompleted: () => finished());
        }

        private static void finished()
        {
            Console.WriteLine("Done!");
        }
    }
}
ابتدا نیاز است تا کوئری متداول LINQ را تبدیل به نمونه‌ی Observable آن کرد. اینکار را توسط متد الحاقی ToObservable که در فضای نام System.Reactive.Linq تعریف شده‌است، انجام می‌دهیم. به این ترتیب، هر زمانیکه که عددی به query اضافه می‌شود، با استفاده از متد Subscribe می‌توان تغییرات آن‌را تحت کنترل قرار داد. برای مثال در اینجا هربار که عددی در بازه‌ی 1 تا 5 تولید می‌شود، یکبار پارامتر onNext اجرا خواهد شد. برای نمونه در مثال فوق، از نتیجه‌ی آن برای نمایش مقدار دریافتی، استفاده شده‌است. سپس توسط پارامتر اختیاری onCompleted، در پایان کار، یک متد خاص را می‌توان فراخوانی کرد. خروجی برنامه در این حالت نیز به صورت ذیل است:
1
2
3
4
5
Done!
البته اگر قصد خلاصه نویسی داشته باشیم، سطر آخر متد Main، با سطر ذیل یکی است:
 observableQuery.Subscribe(Console.WriteLine, finished);

در این مثال ساده صرفا یک Syntax دیگر را نسبت به حلقه‌ی foreach متداول مشاهده کردیم که اندکی فشرده‌تر است. در هر دو حالت نیز عملیات انجام شده در تردجاری صورت گرفته‌اند. اما قابلیت‌ها و ارزش‌های واقعی Rx زمانی آشکار خواهند شد که پردازش موازی و پردازش در تردهای دیگر را در آن فعال کنیم.


الگوی Observer

Rx پیاده سازی کننده‌ی الگوی طراحی شیءگرایی به نام Observer است. برای توضیح آن یک لامپ و سوئیچ برق را درنظر بگیرید. زمانیکه لامپ مشاهده می‌کند سوئیچ برق در حالت روشن قرار گرفته‌است، روشن خواهد شد و برعکس. در اینجا به سوئیچ، subject و به لامپ، observer گفته می‌شود. هر زمان که حالت سوئیچ تغییر می‌کند، از طریق یک callback، وضعیت خود را به observer اعلام خواهد کرد. علت استفاده از callbackها، ارائه راه‌حل‌های عمومی است تا بتواند با انواع و اقسام اشیاء کار کند. به این ترتیب هر بار که شیء observer از نوع متفاوتی تعریف می‌شود (مثلا بجای لامپ یک خودرو قرار گیرد)، نیازی نخواهد بود تا subject را تغییر داد.
در Rx دو اینترفیس معادل observer و subject تعریف شده‌اند. در اینجا اینترفیس IObserver معادل observer است و اینترفیس IObservable معادل subject می‌باشد:
    class Subject : IObservable<int>
    {
        public IDisposable Subscribe(IObserver<int> observer)
        {
        }
    }
کار متد Subscribe، اتصال به Observer است و برای این حالت نیاز به کلاسی دارد که اینترفیس IObserver را پیاده سازی کند.
    class Observer : IObserver<int>
    {
        public void OnCompleted()
        {
        }

        public void OnError(Exception error)
        {
        }

        public void OnNext(int value)
        {
        }
    }
در اینجا OnCompleted زمانی اجرا می‌شود که پردازش مجموعه‌ای از اعداد int پایان یافته باشد. OnError در زمان وقوع استثنایی اجرا می‌شود و OnNext به ازای هر عدد موجود در مجموعه‌ی در حال پردازش، یکبار اجرا می‌شود. البته نیازی به پیاده سازی صریح این اینترفیس نیست و توسط متد توکار Observer.Create می‌توان به همین نتیجه رسید.
مجموعه‌های Observable کلید کار با Rx هستند. در مثال قبل ملاحظه کردیم که با استفاده از متد الحاقی ToObservable بر روی یک کوئری LINQ و یا هر نوع IEnumerable ایی،  می‌توان یک مجموعه‌ی Observable را ایجاد کرد. خروجی کوئری حاصل از آن به صورت خودکار اینترفیس IObservable را پیاده سازی می‌کند که دارای یک متد به نام Subscribe است.
در متد Subscribe کاری که به صورت خودکار صورت خواهد گرفت، ایجاد یک حلقه‌ی foreach بر روی مجموعه‌ی مورد آنالیز و سپس فراخوانی متد OnNext کلاس پیاده سازی کننده‌ی IObserver به ازای هر آیتم موجود در مجموعه است (فراخوانی observer.OnNext). در پایان کار هم فقط return this در اینجا صورت خواهد گرفت. در حین پردازش حلقه، اگر خطایی رخ دهد، متد observer.OnError انجام می‌شود.

در مثال قبل،کوئری LINQ نوشته شده، خروجی از نوع IObservable ندارد. به کمک متد الحاقی ToObservable:
public static System.IObservable<TSource> ToObservable<TSource>(
    this System.Collections.Generic.IEnumerable<TSource> source,
    System.Reactive.Concurrency.IScheduler scheduler)
به صورت خودکار، IEnumerable حاصل از کوئری LINQ را تبدیل به یک IObservable کرده‌ایم. به این ترتیب اکنون کوئری LINQ ما همانند سوئیچ برق عمل می‌کند و با تغییر آیتم‌های موجود در آن، مشاهده‌گرهایی که به آن متصل شده‌اند (از طریق فراخوانی متد Subscribe)، امکان دریافت سیگنال‌های تغییر وضعیت آن‌را خواهند داشت.
البته استفاده از متد Subscribe به نحوی که در مثال قبل ذکر شد، خلاصه شده‌ی الگوی Observer است. اگر بخواهیم دقیقا مانند الگو عمل کنیم، چنین شکلی را خواهد داشت:
 var query = Enumerable.Range(1, 5).Select(number => number);
var observableQuery = query.ToObservable();
var observer = Observer.Create<int>(onNext: number => Console.WriteLine(number));
observableQuery.Subscribe(observer);
ابتدا توسط متد ToObservable یک IObservable (سوئیچ) را ایجاد کرده‌ایم. سپس توسط کلاس Observer موجود در فضای نام System.Reactive، یک IObserver (لامپ) را ایجاد کرده‌ایم. کار اتصال سوئیچ به لامپ در متد Subscribe انجام می‌شود. اکنون هر زمانیکه تغییری در وضعیت observableQuery حاصل شود، سیگنالی را به observer ارسال می‌کند. در اینجا callbacks کار مدیریت observer را انجام می‌دهند.


پردازش نتایج یک کوئری LINQ در تردی دیگر توسط Rx

برای اجرای نتایج متد Subscribe در یک ترد جدید، می‌توان پارامتر scheduler متد ToObservable را مقدار دهی کرد:
using System;
using System.Linq;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Threading;

namespace Rx01
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Thread-Id: {0}", Thread.CurrentThread.ManagedThreadId);
            var query = Enumerable.Range(1, 5).Select(number => number);
            var observableQuery = query.ToObservable(scheduler: NewThreadScheduler.Default);
            observableQuery.Subscribe(onNext: number =>
            {
                Console.WriteLine("number: {0}, on Thread-id: {1}", number, Thread.CurrentThread.ManagedThreadId);
            }, onCompleted: () => finished());
        }

        private static void finished()
        {
            Console.WriteLine("Done!");
        }
    }
}
خروجی این مثال به نحو ذیل است:
 Thread-Id: 1
number: 1, on Thread-id: 3
number: 2, on Thread-id: 3
number: 3, on Thread-id: 3
number: 4, on Thread-id: 3
number: 5, on Thread-id: 3
Done!
پیش از آغاز کار و در متد Main، ترد آی دی ثبت شده مساوی 1 است. سپس هربار که callback متد Subscribe فراخوانی شده‌است، ملاحظه می‌کنید که ترد آی دی آن مساوی عدد 3 است. به این معنا که کلیه نتایج در یک ترد مشخص دیگر پردازش شده‌اند.
NewThreadScheduler.Default در فضای نام System.Reactive.Concurrency واقع شده‌است.


یک نکته
در نگارش‌های آغازین Rx، مقدار scheduler را می‌شد معادل Scheduler.NewThread نیز قرار داد که در نگارش‌های جدید منسوخ شده درنظر گرفته شده و به زودی حذف خواهد شد. معادل‌های جدید آن اکنون NewThreadScheduler.Default، ThreadPoolScheduler.Default و امثال آن هستند.


مدیریت خاتمه‌ی اعمال انجام شده‌ی در تردهای دیگر توسط Rx

یکی از مواردی که حین اجرای نتیجه‌ی callbackهای پردازش شده‌ی در تردهای دیگر نیاز است بدانیم، زمان خاتمه‌ی کار آن‌ها است. برای نمونه در مثال قبل، نمایش Done پس از پایان تمام callbacks انجام شده‌است. فرض کنید، callback پایان عملیات را حذف کرده و متد finished را پس از فراخوانی متد observableQuery.Subscribe قرار دهیم:
observableQuery.Subscribe(onNext: number =>
{
   Console.WriteLine("number: {0}, on Thread-id: {1}", number,     
                              Thread.CurrentThread.ManagedThreadId);
}/*, onCompleted: () => finished()*/);
finished();
اینبار اگر برنامه را اجرا کنیم به خروجی ذیل خواهیم رسید:
 Thread-Id: 1
number: 1, on Thread-id: 3
Done!
number: 2, on Thread-id: 3
number: 3, on Thread-id: 3
number: 4, on Thread-id: 3
number: 5, on Thread-id: 3
این خروجی بدین معنا است که متد  observableQuery.Subscribeدر حین اجرا شدن در تردی دیگر، صبر نخواهد کرد تا عملیات مرتبط با آن خاتمه یابد و سپس سطر بعدی را اجرا کند. بنابراین برای حل این مشکل، تنها کافی است به آن اعلام کنیم که پس از پایان عملیات، onCompleted را اجرا کن.


مدیریت استثناهای رخ داده در حین پردازش مجموعه‌های واکنشگرا

متد Subscribe دارای چندین overload است. تا اینجا نمونه‌ای که دارای پارامترهای onNext و onCompleted بودند را بررسی کردیم. اگر بخواهیم مدیریت استثناءها را نیز در اینجا اضافه کنیم، فقط کافی است از overload دیگر آن که دارای پارامتر onError است، استفاده نمائیم:
observableQuery.Subscribe(
  onNext: number => Console.WriteLine(number),
  onError: exception => Console.WriteLine(exception.Message),
  onCompleted: () => finished());
اگر callback پارامتر onError اجرا شود، دیگر به onCompleted نخواهیم رسید. همچنین دیگر onNext ایی نیز اجرا نخواهد شد.


مدیریت ترد اجرای نتایج حاصل از Rx در یک برنامه‌ی دسکتاپ WPF یا WinForms

تا اینجا مشاهده کردیم که اجرای callbackهای observer در یک ترد دیگر، به سادگی تنظیم پارامتر scheduler متد ToObservable است. اما در برنامه‌های دسکتاپ برای به روز رسانی عناصر رابط کاربری، حتما باید در تردی قرار داشته باشیم که آن رابط کاربری در آن ایجاد شده‌است یا به عبارتی در ترد اصلی برنامه؛ در غیر اینصورت برنامه کرش خواهد کرد. مدیریت این مساله نیز در Rx بسیار ساده‌است. ابتدا نیاز است بسته‌ی Rx-WPF را نصب کرد:
 PM> Install-Package Rx-WPF
سپس توسط متد ObserveOn می‌توان مشخص کرد که نتیجه‌ی عملیات باید بر روی کدام ترد اجرا شود:
 observableQuery.ObserveOn(DispatcherScheduler.Current).Subscribe(...)
روش دیگر آن استفاده از متد ObserveOnDispatcher می‌باشد:
 observableQuery.ObserveOnDispatcher().Subscribe(...)
بنابراین مشخص سازی پارامتر scheduler متد ToObservable، به معنای اجرای query آن در یک ترد دیگر و استفاده از متد ObserveOn، به معنای مشخص سازی ترد اجرای callbackهای مشاهده‌گر است.

و یا اگر از WinForms استفاده می‌کنید، ابتدا بسته‌ی Rx خاص آن‌را نصب کنید:
 PM> Install-Package Rx-WinForms
و سپس ترد اجرای callbackها را SynchronizationContext.Current مشخص نمائید:
 observableQuery.ObserveOn(SynchronizationContext.Current).Subscribe(...)

یک نکته‌
در Rx فرض می‌شود که کوئری شما زمانبر است و callbackهای مشاهده‌گر سریع عمل می‌کنند. بنابراین هدف از callbackهای آن، پردازش‌های سنگین نیست. جهت آزمایش این مساله، اینبار query ابتدایی برنامه را به شکل ذیل تغییر دهید که در آن بازگشت زمانبر یک سری داده شبیه سازی شده‌اند.
 var query = Enumerable.Range(1, 5).Select(number =>
{
   Thread.Sleep(250);
   return number;
});
سپس با استفاده از متد ToObservable، ترد دیگری را برای اجرای واقعی آن مشخص کنید تا در حین اجرای آن برنامه در حالت هنگ به نظر نرسد و سپس نمایش آن‌را به کمک متد ObserveOn، بر روی ترد اصلی برنامه انجام دهید.
مطالب
فشرده سازی اطلاعات توسط الگوریتم Zstandard فیسبوک
Zstandard یا به اختصار Zstd، یک الگوریتم فشرده سازی سریع و بدون افت کیفیت است که از سرعت و قدرت خوبی برخوردار است. این الگوریتم توسط Facebook توسعه داده شده و استفاده می‌شود.
الگوریتم‌های دیگری مانند  Snappy (الگوریتم فشرده سازی گوگل)،  LZ4 (جز سریع‌ترین الگوریتم ها) و LZMA (جز قوی‌ترین الگوریتم‌ها) نیز وجود دارند ولی Zstd تعادل (trade-off) خوبی بین سرعت و میزان فشرده سازی را فراهم می‌کند.
در این مقاله برای فشرده سازی اطلاعات با الگوریتم Zstd، از کتابخانه دات نتی  ZstdNet استفاده خواهیم کرد. علاوه بر این، کتابخانه دیگری نیز به نام  Zstandard.Net  با پشتیبانی از Streaming نیز وجود دارد.
ابتدا لازم است کتابخانه مذکور از Nuget نصب شود.
Install-Package ZstdNet
این کتابخانه شامل کلاسی به نام Compessor برای فشرده سازی است که متد Wrap آن آرایه‌ای byte‌ها را دریافت کرده و پس از فشرده سازی، آرایه‌ای از byte‌ها را باز می‌گرداند. به عنوان مثال :
byte[] sourceData = GetData(); //for example : File.ReadAllBytes(filePath);
byte[] compressedData;
using (var compressor = new Compressor())
{
    compressedData = compressor.Wrap(sourceData);
}  
برای تنظیمات فشرده سازی، از جمله میزان فشرده سازی می‌توان وهله‌ای از CompressionOptions را به سازنده کلاس Compressor ارسال کرد.
کلاس  CompressionOptions در سازنده خود، پارامتری را به نام  compressionLevel از نوع int، دریافت می‌کند که میزان فشرده سازی را مشخص می‌کند. مقدار پیش‌فرض آن 3 است و می‌تواند بین 1 تا 22 ( MaxCompressionLevel) باشد. افزایش این مقدار به معنی افزایش میزان فشرده سازی و طبیعتا افزایش زمان فشرده سازی است. 
var compressor = new Compressor(new CompressionOptions(compressionLevel: 5))
برای خارج کردن از حالت فشرده نیز از کلاس Decompressor و متد Unwrap آن استفاده می‌کنیم که آرایه بایتی فشرده شده را دریافت و پس از Decompress کردن، آرایه بایتی اصلی آن (حالت عادی و غیر فشرده شده) را باز می‌گرداند.
using (var decompressor = new Decompressor())
{
    byte[] sourceData = decompressor.Unwrap(compressedData);
}  
با استفاده از روش فوق می‌توانید قبل از ذخیره سازی فایل‌هایتان آنها را فشرده سازی کنید؛ مخصوصا اگر تصمیم به ذخیره سازی درون دیتابیس را دارید، این امکان می‌تواند به کاهش حجم دیتابیس شما کمک کند.
برای فشرده سازی و ذخیره فایل روی هارد نیز می‌توانید از کلاس کمکی زیر استفاده کنید.
public static class ZstdCompression
{
    public static (decimal savedPercent, long savedSizeKB) Compress(string filePath, string savePath, int compressionLevel = 5)
    {
        var sourceData = File.ReadAllBytes(filePath);
        byte[] compressedData;

        using (var compressor = new Compressor(new CompressionOptions(compressionLevel)))
            compressedData = compressor.Wrap(sourceData);

        File.WriteAllBytes(savePath, compressedData);

        var diff = sourceData.LongLength - compressedData.LongLength;
        var savedpercent = (decimal)diff * 100 / sourceData.LongLength;
        var savedSizeKB = diff / 1024;

        return (savedpercent, savedSizeKB);
    }

    public static void Decompress(string filePath, string savePath)
    {
        var compressedData = File.ReadAllBytes(filePath);
        byte[] decompressedData;

        using (var decompressor = new Decompressor())
            decompressedData = decompressor.Unwrap(compressedData);

        File.WriteAllBytes(savePath, decompressedData);
    }
}  
و برای استفاده :
var compressInfo = ZstdCompression.Compress(@"c:\video.mp4", @"c:\compressedFile");
Console.Write($"Saved : {compressInfo.savedPercent}% ({compressInfo.savedSizeKB} KB)");
//Output => Saved : 57% (7530 KB)

مطالب
تزریق وابستگی‌های Automapper به کمک Autofac
در این مقاله قصد دارم به وسیله Autofac تزریق وابستگی‌های Automapper و همچنین Register کردن فایل‌های Profile Mapper را توضیح دهم.
حتما مقالات مقالات متعدد در رابطه با تزریق وابستگی را که در این سایت وجود دارند، مطالعه کرده‌اید. در این بخش قصد دارم از Autofac (بجای StructureMap) برای تزریق Automapper استفاده کنم.
1. ابتدا ساختار پروژه را بررسی می‌کنیم. بدین منظور یک پروژه جدید را با عنوان AufacDI ایجاد میکنیم. 
2. در این مرحله یک پروژه از نوع Class Library را با عنوان AufacDI.DomainClasses، برای شبیه سازی مدل ایجاد میکنیم. 
3. سپس یک پروژه از نوع Class Library را با عنوان AufacDI.IocConfig برای تعریف تنظیمات تزریق وابستگی ایجاد میکنیم.
4. در ادامه، پروژه‌ای را از نوع Class Library با عنوان AufacDI.MapperProfile برای معرفی Profile‌های Mapper ایجاد میکنیم.
5. همچنین پروژه‌ای را برای ViewModel‌ها تعریف میکنیم؛ با عنوان AufacDI.ViewModel. 
6. و در آخر ایجاد پروژه‌ای برای بخش UI با عنوانAufacDI.WebApplication

در ابتدا نیاز است که بسته‌های زیر را از Nuget دریافت و  نصب کنیم:
PM>Install-Package Autofac
PM>Install-Package Autofac.Mvc5
PM>Install-Package AutoMapper
بسته Autofac را در لایه AufacDI.IocConfig و AufacDI.ConsoleApplication نصب می‌کنیم.
بسته Install-Package Autofac.Mvc5  را برای تزریق وابستگی‌ها در لایه UI استفاده میکنیم.
و بسته AutoMapper را در لایه AufacDI.MapperProfile , AufacDI.IocConfig و  AufacDI.WebApplication  نصب میکنیم (به دلیل اینکه این پروژه برای مثال، Automapper به لایه UI اضافه شده است وگرنه باید در لایه Service ارجاع داده شود).

حال در این بخش به تعاریف داخلی پروژه می‌پردازیم:
لازم است ابتدا یک Domain Class را تعریف کنیم؛ به صورت زیر:
namespace AufacDI.DomainClasses
{
    public class Category
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
}
سپس ViewModel متناظر با آن را تعریف میکنیم:
namespace AufacDI.ViewModel
{
    public class CategoryViewModel
    {
        public int Id { get; set; }
        public int Name { get; set; }
    }
}
سپس یک  Profile را برای مدل نمونه تعریف میکینم. (ارجاعات لازم به DomainClasses و ViewModel داده شود)
using AufacDI.DomainClasses;
using AufacDI.ViewModel;
using AutoMapper;

namespace AufacDI.MapperProfile
{
    public class CategoryProfile : Profile
    {
        public CategoryProfile()
        {
            CreateMap<Category, CategoryViewModel>();
            CreateMap<CategoryViewModel, Category>();
        }
    }
}

حال به بخش اصلی میرسیم؛ یعنی تکمیل بخش IocConfig: (ارجاعات لازم به MapperProfile داده شود)
using AufacDI.MapperProfile;
using Autofac;
using AutoMapper;
using System;
using System.Linq;

namespace AufacDI.IocConfig
{
    public static class IoCContainer
    {
       public static void Register(ContainerBuilder builder)
        {
            // شناسایی پروفایل‌ها براساس نمونه از کلاس پر.وفایل 
            var profiles = from types in typeof(CategoryProfile).Assembly.GetTypes()
                           where typeof(Profile).IsAssignableFrom(types)
                           select (Profile)Activator.CreateInstance(types);

            // رجیستر کردن کلاس‌های پروفایل در اتومپر
            builder.Register(ctx => new MapperConfiguration(cfg =>
            {
                foreach (var profile in profiles)
                    cfg.AddProfile(profile);
            })).SingleInstance().AutoActivate().AsSelf();

            // رجیستر کردن کلاس  MapperConfiguration و ایجاد آن براساس IMapper
            builder.Register(ctx => ctx.Resolve<MapperConfiguration>().CreateMapper()).As<IMapper>().InstancePerRequest();
        }
    }
}

در ادامه با یک مثال، روند کلی را توضیح میدهیم:
            var builder = new ContainerBuilder();

            // تزریق کنترلرها برای تزریف سایر المان‌ها در سازنده
            builder.RegisterControllers(typeof(MvcApplication).Assembly).InstancePerDependency();

            // فراخوانی متد رجیستر برای تزریق وابستگی مپر و کلاس‌های پروفایل آن
            IoCContainer.Register(builder);

            // ایجاد نمونه از سازنده
            var container = builder.Build();
            DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
این بخش، معرفی و تعریف نگاشت‌های تزریق وابستگی می‌باشد.
نمونه‌ای از پیاده سازی در سطح کنترلر
namespace AufacDI.WebApplication.Controllers
{
    public class HomeController : Controller
    {
        private readonly IMapper _mapepr;
        public HomeController(IMapper mapepr)
        {
            _mapepr = mapepr;
        }

        public ActionResult Index()
        {
            // مپ کردن یک کلاس به یک کلاس
            var categoryViewModel = new CategoryViewModel { Id = 1, Name = "News" };
            var categoryModel = _mapepr.Map<CategoryViewModel, Category>(categoryViewModel);

            // مپ کردن لیست از کلاس به لیستی از کلاس
            var categoryListModel = new List<Category>();
            categoryListModel.Add(new Category { Id = 1, Name = "A" });
            categoryListModel.Add(new Category { Id = 2, Name = "B" });
            categoryListModel.Add(new Category { Id = 3, Name = "C" });
            categoryListModel.Add(new Category { Id = 4, Name = "D" });
            categoryListModel.Add(new Category { Id = 5, Name = "E" });

            var categoryListViewModel = categoryListModel.AsQueryable().ProjectTo<CategoryViewModel>(_mapepr.ConfigurationProvider).ToList(); ;

            return View();
        }
    }
}
نکته: برای مپ کردن یک آبجکت به آبجکتی دیگر، از متد Map استفاده می‌شود و برای مپ کردن لیستی از آبجکت‌ها از ProjectTo استفاده می‌شود.
نمونه ای از مثال AufacDI.rar
نظرات مطالب
بررسی روش آپلود فایل‌ها در ASP.NET Core
مرورگرهای جدید با استفاده از ویژگی webkitdirectory، امکان انتخاب یک پوشه و تمام زیر پوشه‌های آن‌را دارند:
<form method="post" asp-action="UploadFiles" asp-controller="Home" enctype="multipart/form-data">
    <input type="file" name="files" webkitdirectory />
    <input type="submit" value="Upload" />
</form>
در این حالت کدهای سمت سرور آن به صورت زیر تغییر می‌کند:
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace UploadFolderASPNETCore.Controllers
{
    public class HomeController : Controller
    {
        private readonly IWebHostEnvironment _environment;
        private const int MaxBufferSize = 0x10000;

        public HomeController(IWebHostEnvironment environment)
        {
            _environment = environment;
        }

        public IActionResult Index()
        {
            return View();
        }

        [HttpPost("[action]")]
        public async Task<IActionResult> UploadFiles(IList<IFormFile> files)
        {
            var uploadsRootFolder = Path.Combine(_environment.WebRootPath, "uploads");
            CreateDir(uploadsRootFolder);

            foreach (var file in files)
            {
                var dirPath = Path.GetDirectoryName(file.FileName);
                CreateDir(Path.Combine(uploadsRootFolder, dirPath));

                var filePath = Path.Combine(uploadsRootFolder, file.FileName);
                using (var fileStream = new FileStream(filePath, FileMode.Create,
                                                        FileAccess.Write,
                                                        FileShare.None,
                                                        MaxBufferSize,
                                                        useAsync: true
                                                        ))
                {
                    await file.CopyToAsync(fileStream);
                }
            }

            return RedirectToAction("Index");
        }

        private void CreateDir(string path)
        {
            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }
        }
    }
}
مطالب
فعال سازی قسمت آپلود تصویر و فایل Kendo UI Editor
یکی دیگر از ویجت‌های Kendo UI یک HTML Editor کامل است به همراه امکانات ارسال فایل، تصویر و ... پشتیبانی از راست به چپ. در ادامه قصد داریم نحوه‌ی مدیریت نمایش لیست فایل‌ها، افزودن و حذف آن‌ها را از طریق این ادیتور بررسی کنیم.


تنظیمات ابتدایی Kendo UI Editor

در ذیل کدهای سمت کاربر فعال سازی مقدماتی Kendo UI را مشاهده می‌کنید. در قسمت tools آن، لیست امکانات و نوار ابزار مهیای آن درج شده‌اند.
دو مورد insertImage و insertFile آن نیاز به تنظیمات سمت کاربر و سرور بیشتری دارند.
<!--نحوه‌ی راست به چپ سازی -->
<div class="k-rtl">
    <textarea id="editor" rows="10" cols="30" style="height: 440px"></textarea>
</div>
 
@section JavaScript
{
    <script type="text/javascript">
        $(function () {
            $("#editor").kendoEditor({
                tools: [
                    "bold", "italic", "underline", "strikethrough", "justifyLeft",
                    "justifyCenter", "justifyRight", "justifyFull", "insertUnorderedList",
                    "insertOrderedList", "indent", "outdent", "createLink", "unlink",
                    "insertImage", "insertFile",
                    "subscript", "superscript", "createTable", "addRowAbove", "addRowBelow",
                    "addColumnLeft", "addColumnRight", "deleteRow", "deleteColumn", "viewHtml",
                    "formatting", "cleanFormatting", "fontName", "fontSize", "foreColor",
                    "backColor", "print"
                ],
                imageBrowser: {
                    messages: {
                        dropFilesHere: "فایل‌های خود را به اینجا کشیده و رها کنید"
                    },
                    transport: {
                        read: {
                            url: "@Url.Action("GetFilesList", "KendoEditorImages")",
                            dataType: "json",
                            contentType: 'application/json; charset=utf-8',
                            type: 'GET',
                            cache: false
                        },
                        destroy: {
                            url: "@Url.Action("DestroyFile", "KendoEditorImages")",
                            type: "POST"
                        },
                        create: {
                            url: "@Url.Action("CreateFolder", "KendoEditorImages")",
                            type: "POST"
                        },
                        thumbnailUrl: "@Url.Action("GetThumbnail", "KendoEditorImages")",
                        uploadUrl: "@Url.Action("UploadFile", "KendoEditorImages")",
                        imageUrl: "@Url.Action("GetFile", "KendoEditorImages")?path={0}"
                    }
                },
                fileBrowser: {
                    messages: {
                        dropFilesHere: "فایل‌های خود را به اینجا کشیده و رها کنید"
                    },
                    transport: {
                        read: {
                            url: "@Url.Action("GetFilesList", "KendoEditorFiles")",
                            dataType: "json",
                            contentType: 'application/json; charset=utf-8',
                            type: 'GET',
                            cache: false
                        },
                        destroy: {
                            url: "@Url.Action("DestroyFile", "KendoEditorFiles")",
                            type: "POST"
                        },
                        create: {
                            url: "@Url.Action("CreateFolder", "KendoEditorFiles")",
                            type: "POST"
                        },
                        uploadUrl: "@Url.Action("UploadFile", "KendoEditorFiles")",
                        fileUrl: "@Url.Action("GetFile", "KendoEditorFiles")?path={0}"
                    }
                }
            });
        });
    </script>
}
در اینجا نحوه‌ی تنظیم مسیرهای مختلف ارسال فایل و تصویر Kendo UI Editor را ملاحظه می‌کنید.
منهای قسمت thumbnailUrl، عملکرد قسمت‌های مختلف افزودن فایل و تصویر این ادیتور یکسان هستند. به همین جهت می‌توان برای مثال کنترلی مانند KendoEditorFilesController را ایجاد و سپس در کنترلر KendoEditorImagesController از آن ارث بری کرد و متد دریافت و نمایش بند انگشتی تصاویر را افزود. به این ترتیب دیگر نیازی به تکرار کدهای مشترک بین این دو قسمت نخواهد بود.


نمایش لیست پوشه‌ها و تصویر در ابتدای باز شدن صفحه‌ی درج تصویر

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


تنظیمات خواندن این فایل‌ها، از قسمت read مربوط به imageBrowser دریافت می‌شود که cache آن نیز به false تنظیم شده‌است تا در این بین مرورگر اطلاعات را کش نکند. این مورد در حین حذف فایل‌ها و پوشه‌ها مهم است. زیرا اگر cache:false تنظیم نشده باشد، حذف یک فایل یا پوشه در سمت کاربر تاثیری نخواهد داشت.
imageBrowser: {
    transport: {
        read: {
            url: "@Url.Action("GetFilesList", "KendoEditorImages")",
            dataType: "json",
            contentType: 'application/json; charset=utf-8',
            type: 'GET',
            cache: false
        }
    }
},
در ادامه نیاز است اکشن متد GetFilesList را به نحو ذیل در سمت سرور تهیه کرد:
namespace KendoUI13.Controllers
{
    public class KendoEditorFilesController : Controller
    {
        //مسیر پوشه فایل‌ها
        protected string FilesFolder = "~/files";
 
        protected string KendoFileType = "f";
        protected string KendoDirType = "d";
 
        [HttpGet]
        public ActionResult GetFilesList(string path)
        {
            path = GetSafeDirPath(path);
            var imagesList = new DirectoryInfo(path)
                                .GetFiles()
                                .Select(fileInfo => new KendoFile
                                {
                                    Name = fileInfo.Name,
                                    Size = fileInfo.Length,
                                    Type = KendoFileType
                                }).ToList();
 
            var foldersList = new DirectoryInfo(path)
                                .GetDirectories()
                                .Select(directoryInfo => new KendoFile
                                {
                                    Name = directoryInfo.Name,
                                    Type = KendoDirType
                                }).ToList();
 
            return new ContentResult
            {
                Content = JsonConvert.SerializeObject(imagesList.Union(foldersList), new JsonSerializerSettings
                {
                    ContractResolver = new CamelCasePropertyNamesContractResolver()
                }),
                ContentType = "application/json",
                ContentEncoding = Encoding.UTF8
            };
        }
 
 
        protected string GetSafeDirPath(string path)
        {
            // path = مسیر زیر پوشه‌ی وارد شده
            if (string.IsNullOrWhiteSpace(path))
            {
                return Server.MapPath(FilesFolder);
            }
 
            //تمیز سازی امنیتی
            path = Path.GetDirectoryName(path);
            path = Path.Combine(Server.MapPath(FilesFolder), path);
            return path;
        } 
    }
}
در اینجا کدهای کلاس پایه KendoEditorFilesController را مشاهده می‌کنید. به این جهت فیلد FilesFolder آن protected تعریف شده‌است تا در کلاسی که از آن ارث بری می‌کند نیز قابل دسترسی باشد. سپس لیست فایل‌ها و پوشه‌های path دریافتی با فرمت لیستی از KendoFile تهیه شده و با فرمت JSON بازگشت داده می‌شوند. ساختار KendoFile را در ذیل مشاهده می‌کنید:
namespace KendoUI13.Models
{
    public class KendoFile
    {
        public string Name { set; get; }
        public string Type { set; get; }
        public long Size { set; get; }
    }
}
- در اینجا Type می‌تواند از نوع فایل با مقدار f و یا از نوع پوشه با مقدار d باشد.
- علت استفاده از CamelCasePropertyNamesContractResolver در حین بازگشت JSON نهایی، تبدیل خواص دات نتی، به نام‌های سازگار با JavaScript است. برای مثال به صورت خودکار Name را تبدیل به name می‌کند.
- پارامتر path در ابتدای کار خالی است. اما کاربر می‌تواند در بین پوشه‌های باز شده‌ی توسط مرورگر تصاویر Kendo UI حرکت کند. به همین جهت مقدار آن باید هربار بررسی شده و بر این اساس لیست فایل‌ها و پوشه‌های جاری بازگشت داده شوند.


مدیریت حذف تصاویر و پوشه‌ها

همانطور که در شکل فوق نیز مشخص است، با انتخاب یک پوشه یا فایل، دکمه‌ای با آیکن ضربدر جهت فراهم آوردن امکان حذف، ظاهر می‌شود. این دکمه متصل است به قسمت destroy تنظیمات ادیتور:
imageBrowser: {
    transport: {
        destroy: {
            url: "@Url.Action("DestroyFile", "KendoEditorImages")",
            type: "POST"
        }
    }
},
این تنظیمات سمت کاربر را باید به نحو ذیل در سمت سرور مدیریت کرد:
namespace KendoUI13.Controllers
{
    public class KendoEditorFilesController : Controller
    {
        //مسیر پوشه فایل‌ها
        protected string FilesFolder = "~/files";
 
        protected string KendoFileType = "f";
        protected string KendoDirType = "d";
 
        [HttpPost]
        public ActionResult DestroyFile(string name, string path)
        {
            //تمیز سازی امنیتی
            name = Path.GetFileName(name);
            path = GetSafeDirPath(path);
 
            var pathToDelete = Path.Combine(path, name);
 
            var attr = System.IO.File.GetAttributes(pathToDelete);
            if ((attr & FileAttributes.Directory) == FileAttributes.Directory)
            {
                Directory.Delete(pathToDelete, recursive: true);
            }
            else
            {
                System.IO.File.Delete(pathToDelete);
            }
 
            return Json(new object[0]);
        } 
    }
}
- استفاده از Path.GetFileName جهت دریافت نام فایل‌ها در اینجا بسیار مهم است. زیرا اگر این تمیز سازی امنیتی صورت نگیرد، ممکن است با کمی تغییر در آن، فایل web.config برنامه، دریافت یا حذف شود.
- پارامتر name دریافتی مساوی است با نام فایل انتخاب شده و path مشخص می‌کند که در کدام پوشه قرار داریم.
- چون در اینجا امکان حذف یک پوشه یا فایل وجود دارد، حتما نیاز است بررسی کنیم، مسیر دریافتی پوشه‌است یا فایل و سپس بر این اساس جهت حذف آن‌ها اقدام صورت گیرد.


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

تنظیمات قسمت create مرورگر تصاویر، مرتبط است به زمانیکه کاربر با کلیک بر روی دکمه‌ی +، درخواست ایجاد یک پوشه‌ی جدید را کرده‌است:
imageBrowser: {
    transport: {
        create: {
            url: "@Url.Action("CreateFolder", "KendoEditorImages")",
            type: "POST"
        }
    }
},
کدهای اکشن متد متناظر با این عمل را در ذیل مشاهده می‌کنید:
namespace KendoUI13.Controllers
{
    public class KendoEditorFilesController : Controller
    {
        //مسیر پوشه فایل‌ها
        protected string FilesFolder = "~/files";
 
        protected string KendoFileType = "f";
        protected string KendoDirType = "d";
 
        [HttpPost]
        public ActionResult CreateFolder(string name, string path)
        {
            //تمیز سازی امنیتی
            name = Path.GetFileName(name);
            path = GetSafeDirPath(path);
            var dirToCreate = Path.Combine(path, name);
 
            Directory.CreateDirectory(dirToCreate);
 
            return KendoFile(new KendoFile
            {
                Name = name,
                Type = KendoDirType
            });
        }
 
        protected ActionResult KendoFile(KendoFile file)
        {
            return new ContentResult
            {
                Content = JsonConvert.SerializeObject(file,
                    new JsonSerializerSettings
                    {
                        ContractResolver = new CamelCasePropertyNamesContractResolver()
                    }),
                ContentType = "application/json",
                ContentEncoding = Encoding.UTF8
            };
        }
    }
}
- در اینجا نیز name مساوی نام پوشه‌ی درخواستی است و path به مسیر تو در توی پوشه‌ی جاری اشاره می‌کند.
- پس از ایجاد پوشه، باید نام آن‌را با فرمت KendoFile به صورت JSON بازگشت داد. همچنین در اینجا Type را نیز باید به d (پوشه) تنظیم کرد.


مدیریت قسمت ارسال فایل و تصویر

زمانیکه کاربر بر روی دکمه‌ی upload file یا بارگذاری تصاویر در اینجا کلیک می‌کند، اطلاعات فایل آپلودی به مسیر uploadUrl ارسال می‌گردد.
imageBrowser: {
    transport: {
        thumbnailUrl: "@Url.Action("GetThumbnail", "KendoEditorImages")",
        uploadUrl: "@Url.Action("UploadFile", "KendoEditorImages")",
        imageUrl: "@Url.Action("GetFile", "KendoEditorImages")?path={0}"
    }
},
دو تنظیم دیگر thumbnailUrl و imageUrl، برای نمایش بند انگشتی و نمایش کامل تصویر کاربرد دارند.
در ادامه کدهای مدیریت سمت سرور قسمت آپلود این ادیتور را مشاهده می‌کنید:
namespace KendoUI13.Controllers
{
    public class KendoEditorFilesController : Controller
    {
        //مسیر پوشه فایل‌ها
        protected string FilesFolder = "~/files";
 
        protected string KendoFileType = "f";
        protected string KendoDirType = "d";

 
        [HttpPost]
        public ActionResult UploadFile(HttpPostedFileBase file, string path)
        {
            //تمیز سازی امنیتی
            var name = Path.GetFileName(file.FileName);
            path = GetSafeDirPath(path);
            var pathToSave = Path.Combine(path, name);
 
            file.SaveAs(pathToSave);
 
            return KendoFile(new KendoFile
            {
                Name = name,
                Size = file.ContentLength,
                Type = KendoFileType
            });
        } 
    }
}
- در اینجا path مشخص می‌کند که در کدام پوشه‌ی تو در تو قرار داریم و file نیز حاوی محتوای ارسالی به سرور است.
- پس از ذخیره سازی اطلاعات فایل، نیاز است اطلاعات فایل نهایی را با فرمت KendoFile به صورت JSON بازگشت دهیم.


ارث بری از KendoEditorFilesController جهت تکمیل قسمت مدیریت تصاویر

تا اینجا کدهایی را که ملاحظه کردید، برای هر دو قسمت ارسال تصویر و فایل کاربرد دارند. قسمت ارسال تصاویر برای تکمیل نیاز به متد دریافت تصاویر به صورت بند انگشتی نیز دارد که به صورت ذیل قابل تعریف است و چون از کلاس پایه KendoEditorFilesController ارث بری کرده‌است، این کنترلر به صورت خودکار حاوی اکشن متدهای کلاس پایه نیز خواهد بود.
using System.Web.Mvc;
 
namespace KendoUI13.Controllers
{
    public class KendoEditorImagesController : KendoEditorFilesController
    {
        public KendoEditorImagesController()
        {
            // بازنویسی مسیر پوشه‌ی فایل‌ها
            FilesFolder = "~/images";
        }
 
        [HttpGet]
        [OutputCache(Duration = 3600, VaryByParam = "path")]
        public ActionResult GetThumbnail(string path)
        {
            //todo: create thumb/ resize image
 
            path = GetSafeFileAndDirPath(path);
            return File(path, "image/png");
        }
    }
}

کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید.
مطالب
کمی درباره دستورات using
شخصی سازی using directives
موقعی که یک کلاس جدید را در VS.NET باز میکنید، فضاهای نامی مشخص و تکراری، همیشه به صورت پیش فرض صدا زده شده‌اند و این فضاهای نام را مایکروسافت بر اساس بیشترین کاربرد و استفاده توسط برنامه نویسان قرار داده است؛ ولی در خیلی از اوقات این فضاهای نام پیش فرض، چنان هم برای خیلی از برنامه نویسان کاربردی نداشته یا با توجه به برنامه هایی که می‌نویسند همیشه متفاوت هست و هربار مجبورند فضاهای نام خاصی را صدا بزنند.
 برای مثال فضای نام System.ComponentModel.DataAnnotations را در نظر بگیرید که برنامه نویس میخواهد برای مدل‌های نوشته شده خود از تگ‌های متا استفاده کند و باید در هرکلاس ساخته شده، یکبار مورد بالا را صدا بزند که بیشتر باعث کند شدن کار برنامه نویس می‌شود. پس باید کاری کنیم که پیش فرض‌های فضای نام به آنچه خودمان میخواهیم تغییر پیدا کند.
برای این منظور، به محل نصب ویژوال استودیو رفته و مسیر زیر را دنبال کنید (به مسیر دقت کنید ،در اینجا زبان سی شارپ انتخاب شده است):
X:\...\IDE\ItemTemplates\CSharp\Code\1033
در اینجا تعدادی دایرکتوری با اسامی آشنا می‌بینید که داخل هر کدام از آن‌های یک فایل به اسم class.cs هست و اگر آن را باز کنید یک نمونه یا قالب برای using‌‌ها قابل مشاهده است. برای مثال ما وارد دایرکتوری class می‌شویم و فایل class.cs را باز می‌کنیم:
using System;
using System.Collections.Generic;
$if$ ($targetframeworkversion$ >= 3.5)using System.Linq;
$endif$using System.Text;
$if$ ($targetframeworkversion$ >= 4.5)using System.Threading.Tasks;
$endif$
namespace $rootnamespace$
{
    class $safeitemrootname$
    {
    }
}
الان باید با یک نگاه به الگو، مشخص باشد که چکار باید بکنید.
یک سری از فضاهای نام که در تمامی فریمورک‌ها استفاده میشوند به همان شکل عادی نوشته شده‌اند. ولی آنهایی که از نسخه‌ی خاصی از یک فریم ورک اضافه شده‌اند باید توسط شرط مورد نظر اضافه شده و اعلام شود که این فضای نام از چه نسخه‌ی فریم ورکی به بعد باید اضافه گردد:
$if$ ($targetframeworkversion$ >= 3.5)using System.Linq;//فضای نام مورد نظر
$endif$
حالا تغییرات را ذخیره کنید و در VS.NET یک کلاس جدید را ایجاد کنید. همانطور که خواهید دید، تغییرات شما اعمال شده‌است. برای اعمال تغییرات نیازی به بستن و باز کردن مجدد VS.NET نمی‌باشد. در لحظه ایجاد کلاس الگو خوانده می‌شود.
حال در همان دایرکتوری سی شارپ دقت کنید، می‌بینید که برای موارد دیگری هم فایل هایی وجود دارند. برای مثال برای اینترفیس‌ها یا silverlight و ... که هر کدام را می‌توانید جداگانه تغییر دهید.
نکته:احتمال دارد در نسخه‌های متفاوت به خصوص پایین‌تر مثل نسخه 8 ویژوال استودیو ، فایل class.cs به صورت zip باشد که بعد از تغییرات باید دوباره به حالت zip بازگردانده شود.

حذف فضای نام‌های اضافی
هر موقع که کلاس جدیدی میسازیم، namespace‌ها به صورت پیش فرض که در بالا اشاره کردیم وجود دارند و شاید اصلا در آن کلاس از آن‌ها استفاده نمی‌کنیم یا حتی خودمان در حین نوشتن کدها چند namespace خاص را اضافه می‌کنیم که شاید در طول برنامه نویسی چندتایی را بلا استفاده بگذاریم. برای همین همیشه فضای نام هایی صدا زده شده‌اند که اصلا در آن کلاس استفاده نشده‌اند. پس برای همین بهتر هست که این رفرنس‌های بلا استفاده را پیدا کرده و آن‌ها را حذف کنیم.
شاید این سوال برای بعضی‌های پدید بیاد که چرا باید این‌ها را حذف کنیم، چون کاری هم با ما ندارند و ما هم کاری با آن‌ها نداریم؟
این کار چند علت میتواند داشته باشد:
  • تمیزکاری کد و خلوت شدن فضای کد‌نویسی
  • ممکن هست بعدها گیج کننده شود که من چرا از این‌ها استفاده کردم؟ در آینده با نگاه به یک کد تمیزتر متوجه میشوید یک کد از چه چیزهایی برای انجام کارش بهره‌مند شده و هم اینکه در کارهای گروهی و تیمی هم این مورد به شدت تاثیرگذار هست.
  • باعث کند شدن تحلیل‌های ایستا میشه (اینجا و اینجا )
  • کمپایل شدن کد کندتر میشه
  • موقع تست برنامه، اجرای اولیه کندتر خواهد بود چون CLR باید این نوع موارد را شناسایی و حذف کند
همه موارد بالا در مورد رفرنس‌های موجود یا همان dll‌های موجود در شاخه‌ی Bin و References هم صدق می‌کند.
برای حذف فضاهای نام اضافی در یک صفحه می‌توانید از طریق این مسیر انجام بدید:
Edit>IntelliSense >Organize Usings>Remove Unused using
برای مرتب سازی هم گزینه Sort Usings و انجام هر دو کار Remove and Sort موجود هست.
البته اگه روی صفحه هم راست کلیک کنید گزینه Organize Usings هم وجود دارد.
می توانید از ابزارهایی چون  Power tools Extensions هم استفاده کنید (در صورتی که ویژوال استودیوی شما گزینه‌های مورد نظر را ندارد، این ابزار را نصب نمایید) 
در صورتی که از ابزارهایی چون  telerik  یا  devexpress  استفاده می‌کنید یا از هر ابزار اضافی که بر روی IDE نصب می‌شود، عموما چنین گزینه هایی حتی با امکانات وسیعتر وجود دارند. مثلا  whole tomato  هم یکی از این ابزارهاست.
این نکته را هم خاطر نشان کنم در صورتیکه فضاهای نامی بین پیش پردازنده ها که در قبل توضیح دادیم محصور شده باشند، حذف نخواهند شد و همانطور باقی خواهند ماند.
در مورد کامنت‌های بین using‌ها به قطعه کد زیر نگاه کنید:
using System;
/* Comment before remains */
using /* Comment between removed */ System.Linq;
// Comment after remains
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("My Example");
}
}
}
و حالا بعد از حذف فضای نام‌های اضافی
using System;
/* Comment before remains */
// Comment after remains
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("My Example");
}
}
}
برای اینکه این عمل را بتونید در کل صفحات اعمال کنید می‌توانید از cleanup selected code هم استفاده کنید؛ به جز اینکه فضاهای نام اضافی را هم پاک می‌کند، کلیه کدهای شما را در قالبی شکیل‌تر و خواناتر قرار خواهد داد.
با کلید‌های Ctrl+k+d سند انتخابی و با کلیدهای ترکیبی Ctrl+k+f هم محدوده انتخاب شده قالب بندی می‌شود.
یکی دیگر از ابزارهایی که می‌توان با آن‌ها به کد سر و سامان بهتری داد، افزونه‌ی codemaid  هست.

ویژگی سی شارپ 6 در مورد Using
فرض کنید ما یک کلاس ایستا به نام utilities ایجاد کردیم که یک متد به اسم addints دارد. حالا و این کلاس در namespace به نام   SomeNamespace قرار دارد. مطمئنا در این حالت ما ابتدا فضای نام را using میکنیم و سپس در کد کلاس، متد را به شکل زیر صدا میزنیم:
using System;
using SomeNamespace;
 
namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            int sum = Utility.AddInts(5, 2);
 
            Console.ReadLine();
        }
    }
}
ولی در سی شارپ 6 میتوانید بعد از فضای نام، یک . گذاشته و سپس اسم کلاس ایستا static را بیاورید و در کد مستقیما متد دلخواه خود را صدا بزنید.
به شکل زیر دقت کنید:
using System;
using SomeNamespace.Utility;
 
namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            int sum = AddInts(5, 2);
 
            Console.ReadLine();
        }
    }
}
نکته پایانی:در visual studio 2014 فضاهای نام اضافی به رنگ خاکستری نمایش داده می‌شوند.

منابع:
مطالب دوره‌ها
ایجاد یک اسمبلی جدید توسط Reflection.Emit
مطابق استاندارد ECMA-335 قسمت دوم آن، یک اسمبلی از یک یا چند ماژول تشکیل می‌شود. هر ماژول از تعدادی نوع، enum و delegate تشکیل خواهد شد و هر نوع دارای تعدادی متد، فیلد، خاصیت و غیره می‌باشد. به همین جهت در حین کار با Reflection.Emit نیز این مراحل رعایت می‌شوند. ابتدا یک اسمبلی (AppDomain.DefineDynamicAssembly) ایجاد خواهد شد (یا از اسمبلی موجود استفاده می‌شود). سپس یک ماژول (AssemblyBuilder.DefineDynamicModule) را باید به آن اضافه کنیم (یا از ماژول اسمبلی جاری استفاده نمائیم). در ادامه یک Type باید به این ماژول اضافه شود (ModuleBuilder.DefineType) و اکنون می‌توان به این نوع جدید، سازنده (TypeBuilder.DefineConstructor)، متد (TypeBuilder.DefineMethod)، فیلد (TypeBuilder.DefineField)، خاصیت (TypeBuilder.DefineProperty) و رخداد (TypeBuilder.DefineEvent) اضافه کرد.


using System;
using System.Reflection;
using System.Reflection.Emit;

namespace FastReflectionTests
{
    class Program
    {
        static void Main(string[] args)
        {
            var name = "HelloWorld.exe";
            var assemblyName = new AssemblyName(name);
            // ایجاد یک اسمبلی جدید با قابلیت ذخیره سازی آن
            var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
                                            name: assemblyName,
                                            access: AssemblyBuilderAccess.RunAndSave);
            // افزودن یک ماژول به اسمبلی
            var moduleBuilder = assemblyBuilder.DefineDynamicModule(name);
            // تعریف یک کلاس در این ماژول
            var programmClass = moduleBuilder.DefineType("Program", TypeAttributes.Public);
            // افزودن یک متد به این کلاس
            // این متد خروجی ندارد اما ورودی آن شبیه به متد اصلی یک برنامه کنسول است
            var mainMethod = programmClass.DefineMethod(name: "Main",
                                            attributes: MethodAttributes.Public | MethodAttributes.Static,
                                            returnType: null,
                                            parameterTypes: new Type[] { typeof(string[]) });
            // تعیین بدنه متد اصلی برنامه
            var il = mainMethod.GetILGenerator();
            il.Emit(OpCodes.Ldstr, "Hello World!");
            il.Emit(OpCodes.Call, (typeof(Console)).GetMethod("WriteLine", new Type[] { typeof(string) }));
            il.Emit(OpCodes.Call, (typeof(Console)).GetMethod("ReadKey", new Type[0]));
            il.Emit(OpCodes.Pop);
            il.Emit(OpCodes.Ret);

            // تکمیل کار ایجاد نوع جدید
            programmClass.CreateType();

            // تعیین نقطه شروع فایل اجرایی برنامه کنسول تهیه شده
            assemblyBuilder.SetEntryPoint(((Type)programmClass).GetMethod("Main"));

            // ذخیره سازی این اسمبلی بر روی دیسک سخت
            assemblyBuilder.Save(name);
        }
    }
}
مراحلی را که توضیح داده شد، در کدهای فوق ملاحظه می‌کنید. انتخاب حالت دسترسی AssemblyBuilderAccess.RunAndSave سبب می‌شود تا بتوان نتیجه حاصل را ذخیره کرد. فایل Exe نهایی را اگر در برنامه ILSpy باز کنیم چنین شکلی دارد:
using System;
public class Program
{
    public static void Main(string[] array)
    {
       Console.WriteLine("Hello World!");
       Console.ReadKey();
    }
}
مطالب
الگوی بازدیدکننده Visitor Pattern
این الگو یکی دیگر از الگوهای رفتاری است که به قاعده OCP یا Open Closed Principle کمک بسیاری می‌کند. این الگو برای زمانی مناسب است که ما سعی بر این داریم تا یک سری الگوریتم‌های متفاوت را بر روی یک سری از اشیاء پیاده سازی کنیم. به عنوان مثال تصور کنید که ما در یک سازمان افراد مختلفی را از مدیریت اصلی گرفته، تا ساده‌ترین کارمندان، داریم و برای محاسبه حقوق و مالیات و ... نیاز است تا برای هر کدام دستور العمل‌هایی را اجرا کنیم  و ممکن است در آینده تعداد این دستور العمل‌ها بالاتر هم برود.
در این مثال ما سه گروه Manager,Employee و Worker را داریم که می‌خواهیم با استفاده از این الگو برای هر کدام به طور جداگانه، حقوق و دستمزد و اضافه کاری را محاسبه کنیم. با توجه به اینکه فرمول هر یک جداست و این احتمال نیز وجود دارد که هر کدام خواص مخصوص به خود را داشته باشند که در دیگری وجود ندارد و در آینده این احتمال می‌رود که سمت جدید یا دستورالعمل‌های جدیدی اضافه شود، بهترین راه حل استفاده از الگوی Visitor است.

الگوی visitor دو بخش مهم دارد؛ یکی Element که قرار است کار روی آن انجام شود. مثل سمت‌های مختلف و دیگری Visitor هست که همان دستورالعمل‌هایی چون محاسبه حقوق و دستمزد و ... است که روی المان‌ها صورت می‌گیرد.
ابتدا برای هر کدام یک اینترفیس را با مشخصات زیر می‌سازیم:
 public interface IElement
    {
        void Accept(IElementVisitor visitor);
    }

    public interface IElementVisitor
    {
        void Visit(Manager manager);
        void Visit(Employee manager);
        void Visit(Worker manager);
    }
همانطور که می‌بینید در کلاس Visitor سه متد هستند که سه کلاس مدیر، کارمند و کارگر را که مشتق شده از اینترفیس Element هستند، به صورت آرگومان می‌پذیرند. توصیف هر کلاس المان به شرح زیر است:
 public class  Manager: IElement
    {
        public int WorkingHour = 8;
        public int Wife = 1;
        public int Children = 3;
        public int OffDays = 6;
        public int OverHours = 12;

        public void Accept(IElementVisitor visitor)
        {
            visitor.Visit(this);
        }
    }

public class Employee: IElement
    {
        public int WorkingHour = 8;
        public int Wife = 1;
        public int Children = 3;
        public int OffDays = 6;
        public int OverHours = 12;

        public void Accept(IElementVisitor visitor)
        {
            visitor.Visit(this);
        }
    }

public class Worker:IElement
    {
        public int WorkingHour = 8;
        public int Wife = 1;
        public int Children = 3;
        public int OffDays = 6;
        public int OverHours = 12;

        public void Accept(IElementVisitor visitor)
        {
            visitor.Visit(this);
        }
    }
ما اطلاعات هر کلاس را در این مثال، مشابه گذاشته‌ایم تا نتیجه فرمول را ببینیم. ولی هیچ الزامی به رعایت آن نیست.
حال وقت آن رسیده تا از روی کلاس Visitor، برای حقوق، دستمزد و اضافه کاری، کلاس‌های جدیدی را بسازیم:
 class SalaryCalculator:IElementVisitor
    {
        public void Visit(Manager manager)
        {
            var salary = manager.WorkingHour*10000;
            salary += manager.Wife*25000;
            salary += manager.Children*20000;
            salary -= manager.OffDays*5000;
            Console.WriteLine("Manager's Salary is " + salary);
        }

        public void Visit(Employee employee)
        {
            var salary = employee.WorkingHour * 7000;
            salary += employee.Wife * 15000;
            salary += employee.Children * 10000;
            salary -= employee.OffDays * 6000;
            Console.WriteLine("Employee's Salary is " + salary);
        }

        public void Visit(Worker worker)
        {
            var salary = worker.WorkingHour * 6000;
            salary += worker.Wife * 5000;
            salary += worker.Children * 2000;
            salary -= worker.OffDays * 7000;
            Console.WriteLine("Worker's Salary is " + salary);
        }
    }

    class WageCalculator:IElementVisitor
    {
        public void Visit(Manager manager)
        {
            var wage = manager.OverHours*30000;
            Console.WriteLine("Employee's wage is " + wage);
        }

        public void Visit(Employee employee)
        {
            var wage = employee.OverHours * 20000;
            Console.WriteLine("Employee's wage is " + wage);
        }

        public void Visit(Worker worker)
        {
            var wage = worker.OverHours * 15000;
            Console.WriteLine("Employee's wage is " + wage);
        }
    }
اکنون نیاز است تا ارتباط بین المان‌ها و بازدید کننده‌ها را طوری برقرار کنیم که برای تغییر آن‌ها در آینده، مشکلی نداشته باشیم. به همین جهت یک کلاس جدید به نام سیستم مالی ایجاد می‌کنیم:
class FinancialSystem
    {
        private readonly IList<IElement> _elements;

        public FinanceSystem()
        {
            _elements=new List<IElement>();
        }

        public void Attach(IElement element)
        {
            _elements.Add(element);
        }

        public void Detach(IElement element)
        {
            _elements.Remove(element);
        }

        public void Accept(IElementVisitor visitor)
        {
            foreach (var element in _elements)
            {
                element.Accept(visitor);
            }
        }
    }
در این روش تمام المان‌ها را داخل یک لیست قرار داده و سپس با استفاده از متد Accept، یکی از کلاس‌های مشتق شده از Visitor را به آن نسبت می‌دهیم که وظیفه آن صدا زدن متد Accept درون المان هاست. وقتی متد Accept المان‌ها صدا زده شد، شیء، المان را به متد Visit در Visitor داده و فرمول را روی آن اجرا می‌کند.
بدنه اصلی:
IElement manager=new Manager();
IElement employee=new Employee();
IElement worker=new Worker();

var fine=new FinancialSystem();
fine.Attach(manager);
fine.Attach(employee);
fine.Attach(worker);

fine.Accept(new SalaryCalculator());
fine.Accept(new WageCalculator());
نتیجه خروجی:
Manager's Salary is 135000
Employee's Salary is 65000
Worker's Salary is 17000
Manager's wage is 360000
Employee's wage is 240000
Worker's wage is 180000