مطالب
آشنایی با CLR: قسمت پنجم
اجرای کدهای اسمبلی

همانطور که قبلا ذکر کردیم یک اسمبلی شامل کدهای IL و متادیتا هاست. IL یک زبان غیر وابسته به معماری سی پی یو است که مایکروسافت پس از مشاوره‌های زیاد از طریق نویسندگان کامپایلر و زبان‌های آکادمی و تجاری آن را ایجاد کرده است. IL یک زبان کاملا سطح بالا نسبت به زبان‌های ماشین سی پی یو است. IL می‌تواند به انواع اشیاء دسترسی داشته و آن‌ها را دستکاری نماید و شامل دستورالعمل هایی برای ایجاد و آماده سازی اشیاست. صدا زدن متدهای مجازی بر روی اشیاء و دستکاری المان‌های یک آرایه به صورت مستقیم، از جمله کارهایی است که انجام می‌دهد. همچنین شامل دستوراتی برای صدور و کنترل استثناء هاست . شما می‌توانید IL را به عنوان یک زبان ماشین شیء گرایی تصور کنید.
معمولا برنامه نویس‌ها در یک زبان سطح بالا چون سی شارپ به نوشتن می‌پردازند و کمپایلر کد IL آن‌ها را ایجاد می‌کند و این کد IL می‌تواند به صورت اسمبلی نوشته شود. به همین علت مایکروسافت ابزار ILASM.exe و برای دی اسمبل کردن ILDASM.exe را ارائه کرده است.
این را همیشه به یاد داشته باشید که زبان‌های سطح بالا تنها به زیر قسمتی از قابلیت‌های CLR دسترسی دارند؛ ولی در IL Assembly توسعه دهنده به تمامی قابلیت‌های CLR دسترسی دارد. این انتخاب شما در زبان برنامه نویسی است که می‌خواهید تا چه حد به قابلیت‌های CLR دسترسی داشته باشید. البته یکپارچه بودن محیط در CLR باعث پیوند خوردن کد‌ها به یکدیگر می‌شود. برای مثال می‌توانید قسمتی از یک پروژه که کار خواندن و نوشتن عملیات را به عهده دارد بر دوش #C قرار دهید و محاسبات امور مالی را به APL بسپارید.

برای اجرا شدن کدهای IL، ابتدا CLR باید بر اساس معماری سی پی یو کد ماشین را به دست آورد که وظیفه‌ی تبدیل آن بر عهده Jit یا Just in Time است . شکل زیر نحوه انجام این کار را انجام می‌دهد:



قبل از اجرای متد Main، ابتدا CLR به دنبال ارجاعاتی می‌گردد که در این متد استفاده شده است تا یک ساختار داده داخلی، برای ارجاعات این متد در حافظه تشکیل شود. در شکل بالا یک ارجاع وجود دارد و آن هم شیء کنسول است. این ساختار داده داخلی شامل یک مدخل ورودی (آدرس آغاز در حافظه) به ازای هر متد تعریف شده در نوع کنسول است. هر مدخل ورودی شامل آدرسی است که متدها در آنجا پیاده سازی شده‌اند. موقعیکه این آماده سازی انجام می‌گیرد، آن‌ها را به سمت یک تابع مستند نشده در خود CLR به نام Jit Compiler ارسال می‌کند.
موقعیکه کنسول اولین متدش مثلا WriteLine را فراخوانی می‌کند، کامپایلر جیت صدا زده می‌شود. تابع کامپایلر جیت مسئولیت تبدیل کدهای IL را به کدهای بومی آن پلتفرم، به عهده دارد. از آنجایی که عمل کامپایل در همان لحظه یا در جا اتفاق می‌افتد (Just in time)، عموم این کامپایر را Jitter یا Jit Compiler می‌نامند.

موقعیکه صدا زدن آن متد به سمت jit انجام شد، جیت متوجه می‌شود که چه متدی درخواست شده و نحوه‌ی تعریف آن متد به چه صورتی است. جیت هم در متادیتای یک اسمبلی به جست و جو پرداخته و کدهای IL آن متد را دریافت می‌کند. سپس کدها را تایید و عملیات کامپایل به سمت کدهای بومی را آغاز می‌کند. در ادامه این کدهای بومی را در قطعه‌ای از حافظه ذخیره می‌کند. سپس جیت به جایی بر می‌گردد که CLR از آنجا جیت را وارد کار کرده؛ یعنی مدخل ورودی متد writeline و سپس آدرس آن قطعه حافظه را که شامل کد بومی است، بجای آن قطعه که به کد IL اشاره می‌کند، جابجا می‌کند و کد بومی شده را اجرا و نهایتا به محدوده‌ی main باز می‌گردد.
در شکل زیر مجددا همان متد صدا زده شده است. ولی از آنجا که قبلا کد کامپایل شده را به دست آوردیم، از همان استفاده می‌کنیم و دیگر تابع جیت را صدا نمی‌زنیم.


  توجه داشته باشید، در متدهای چند ریختی که شکل‌های متفاوتی از پارامترها را دارند، هر کدام کمپایل جداگانه‌ای صورت می‌گیرد. یعنی برای متدهای زیر جیت برای هر کدام جداگانه فراخوانی می‌شود.
WriteLine("Hello");
WriteLine();

در مقاله‌ی آینده عملکرد جیت را بیشتر مورد بررسی قرار می‌دهیم و در مورد دیباگ کردن و به نظرم برتری CLR  را نسبت به زبان‌های مدیریت نشده، بررسی می‌کنیم.

مطالب
Functional Programming یا برنامه نویسی تابعی - قسمت اول
 آشنایی

این قسمت از مقاله به ایده اصلی برنامه نویسی تابعی و دلیل وجودی آن خواهد پرداخت. هیچ شکی نیست که بزرگترین چالش در توسعه نرم افزار‌های بزرگ، پیچیدگی آن است. تغییرات همیش اجتناب ناپذیر هستند. به خصوص زمانی که صحبت از پیاده سازی امکان جدیدی باشد، پیچیدگی اضافه خواهد شد. در نتیجه منجر به سخت شدن فهمیدن کد می‌شود، زمان توسعه را بالاتر می‌برد و باگ‌های ناخواسته را به وجود خواهد آورد. همچنین تغییر هر چیزی در دنیای نرم افزار بدون به وجود آوردن رفتار‌های ناخواسته و یا اثرات جانبی، تقریبا غیر ممکن است. در نهایت همه این موارد می‌توانند سرعت توسعه را پایین برده و حتی باعث شکست پروژه‌های نرم افزاری شوند. سبک‌های کد نویسی دستوری (Imperative) مانند برنامه نویسی شیء گرا، میتوانند به کاهش این پیچیدگی‌ها تا حد خوبی کمک کنند. البته در صورتیکه به طور صحیحی پیاده شوند. در واقع با ایجاد Abstraction در این مدل برنامه نویسی، پیچیدگی‌ها را مخفی میکنیم.


سیر تکاملی الگو‌های برنامه نویسی


برنامه نویسی شیء گرا در خون برنامه نویس‌های سی شارپ جاری است؛ ما معمولا ساعت‌ها درباره اینکه چگونه میتوانیم با استفاده از ارث بری و ترتیب پیاده کلاس‌ها، یک هدف خاص برسیم، بر روی کپسوله سازی تمرکز میکنیم و انتزاع (Abstraction) و چند ریختی ( Polymorphism ) را برای تغییر وضعیت برنامه استفاده میکنیم. در این مدل همیشه احتمال این وجود دارد که چند ترد به صورت همزمان به یک ناحیه از حافظه دسترسی داشته باشند و تغییری در آن به وجود بیاورند و باعث به وجود آمدن شرایط Race Condition شوند. البته همگی به خوبی میدانیم که میتوانیم یک برنامه‌ی کاملا Thread-Safe هم داشته باشیم که به خوبی مباحث همزمانی و همروندی را مدیریت کند؛ اما یک مساله اساسی در مورد کارآیی باقی می‌ماند. گرچه Parallelism به ما کمک میکند که کارآیی برنامه خود را افزایش دهیم، اما refactor کردن کد‌های موجود، به حالت موازی، کاری سخت و پردردسر خواهد بود.


راهکار چیست؟

برنامه نویسی تابعی، یک الگوی برنامه نویسی است که از یک ایده قدیمی (قبل از اولین کامپیوتر‌ها !) برگرفته شده‌است؛ زمانیکه دو ریاضیدان، یک تئوری به نام  lambda calculus را معرفی کردند، که یک چارچوب محاسباتی می‌باشد؛ عملیاتی ریاضی را انجام می‌دهد و نتیجه را محاسبه میکند، بدون اینکه تغییری را در وضعیت داده‌ها و وضعیت، به وجود بیاورد. با این کار، فهمیدن کد‌ها آسانتر خواهد بود و اثرات جانبی را کمتر خواهد کرد، همچین نوشتن تست‌ها ساده‌تر خواهند شد.


زبان‌های تابعی

جالب است اگر زبان‌های برنامه نویسی را که از برنامه نویسی تابعی پشتیبانی میکنند، بررسی کنیم، مانند Lisp , Clojure, Erlang, Haskel، هر کدام از این زبان‌ها جنبه‌های مختلفی از برنامه نویسی تابعی را پوشش میدهند. #F یک عضو از خانواده ML می‌باشد که بر روی دات نت فریمورک در سال 2002 پیاده سازی شده. ولی جالب است بدانید بیشتر زبان‌های همه کاره مانند #C به اندازه کافی انعطاف پذیر هستند تا بتوان الگوهای مختلفی را توسط آن‌ها پیاده کرد. از آنجایی که اکثرا ما از #C برای توسعه نرم افزارهایمان استفاده میکنیم، ترکیب ایده‌های برنامه نویسی تابعی میتواند راهکار جالبی برای حل مشکلات ما باشد.


مفاهیم پایه ای

قبلا درباره توابع ریاضی صحت کردیم. در زبان‌های برنامه نویسی هم ایده همان است؛ ورودی‌های مشخص و خروجی مورد انتظار، بدون تغییری در حالت برنامه. به این مفاهیم شفافیت و صداقت توابع میگوییم که در ادامه با آن بیشتر آشنا میشویم. به این نکته توجه داشته باشید که منظور از تابع در #C فقط Method نیست؛ Func , Action , Delegate هم نوعی تابع هستند.


شفافیت توابع (Referential Transparency)

به طور ساده با نگاه کردن به ورودی‌های تابع و نام آن‌ها باید بتوانیم کاری را که انجام میدهد، حدس بزنیم. یعنی یک تابع باید بر اساس ورودی‌های آن کاری را انجام دهد و نباید یک پارامتر Global آن را تحت تاثیر قرار دهد. پارامتر‌های Global میتوانند یک Property در سطح یک کلاس باشند، یا یک شیء که وضعیت آن تحت کنترل تابع نیست؛ مانند شی DateTime. به مثال زیر توجه کنید:
public int CalculateElapsedDays(DateTime from)
{
   DateTime now = DateTime.Now;
   return (now - from).Days;
}
این تابع شفاف نیست. چرا؟ چون امروز، یک خروجی را میدهد و فردا یک خروجی دیگر را! به بیان دیگر وابسته به یک شیء سراسری DateTime.Now است.
آیا میتوانید این تابع را شفاف کنیم؟ بله!
چطور؟ به سادگی! با تغییر پارامتر‌های ورودی:
 public static int CalculateElapsedDays(DateTime from, DateTime now) => (now - from).Days;
در مثال بالا، ما وابستگی به یک شیء سراسری را از بین بردیم.


صداقت توابع (Function Honesty)

صداقت یک تابع یعنی یک تابع باید همه اطلاعات مربوط به ورودی‌ها و خروجی‌ها را پوشش دهد. به این مثال دقت کنید:
public int Divide(int numerator, int denominator)
{
   return numerator / denominator;
}
آیا این تابع شفاف است؟ بله.
آیا این همه مواردی را که از آن انتظار داریم پوشش میدهد؟ احتمالا خیر!

اگر دو عدد صحیح را به این تابع بفرستیم، احتمالا مشکلی پیش نخواهد آمد. اما همانطور که حدس میزنید اگر پارامتر دوم 0 باشد چه اتفاقی خواهد افتاد؟
var result = Divide(1,0);
قطعا خطای Divide By Zero را خواهیم گرفت. امضای این تابع به ما اطلاعاتی درباره خطاهای احتمالی نمی‌دهد.

چگونه مشکل را حل کنیم؟ تایپ ورودی را به شکل زیر تغییر دهیم:
public static int Divide(int numerator, NonZeroInt denominator)
{
   return numerator / denominator.Value;
}
NonZeroInt یک نوع ورودی اختصاصی است که خودمان طراحی کرده‌ایم که تمام مقادیر را به جز صفر، قبول میکند.

به طور کلی تمرین زیادی لازم داریم تا بتوانیم با این مفاهیم به طور عمیق آشنا شویم. در این مقاله قصد دارم جنبه‌های ابتدایی برنامه نویسی تابعی مانند  Functions as first class values ، High Order Functions و Pure Functions را مورد بررسی قرار دهم.

Functions as first-class values

ترجمه فارسی این کلمه ما را از معنی اصلی آن خیلی دور می‌کند؛ احتمالا یک ترجمه ساد‌ه‌ی آم میتواند «تابع، ارزش اولیه کلاس» باشد!
وقتی توابع first-class values باشند، یعنی می‌توانند به عنوان ورودی سایر توابع استفاده شوند، می‌توانند به یک متغیر انتساب داده شوند، دقیقا مثل یک مقدار. برای مثال:
Func<int, bool> isMod2 = x => x % 2 == 0;
var list = Enumerable.Range(1, 10);
var evenNumbers = list.Where(isMod2);
در این مثال، تابع، First-class value می‌باشد؛ چون شما می‌توانید آن را به یک متغیر نسبت دهید و به عنوان ورودی به تابع بعدی بدهید. در مدل برنامه نویسی تابعی، تلقی شدن توابع به عنوان مقدار، ضروری است. چون به ما امکان تعریف توابع High-Order را میدهد.


High-Order Functions (HOF)

توابع مرتبه بالا! یک یا چند تابع را به عنوان ورودی می‌گیرند و یک تابع را به عنوان نتیجه بر میگرداند. در مثال بالا Extension Method ، Where یک تابع High-Order می‌باشد.
پیاده سازی Where احتمالا به شکل زیر می‌باشد:
public static IEnumerable<T> Where<T>(this IEnumerable<T> ts, Func<T, bool> predicate)
{
   foreach (T t in ts)
      if (predicate(t))
         yield return t;
}
1. وظیفه چرخیدن روی آیتم‌های لیست، مربوط به Where می‌باشد.
2. ملاک تشخیص اینکه چه آیتم‌هایی در لیست باید وجود داشته باشند، به عهده متدی می‌باشد که آن را فراخوانی میکند.

در این مثال، تابع Where، تابع ورودی را به ازای هر المان، در لیست فراخوانی میکند. این تابع می‌تواند طوری طراحی شود که تابع ورودی را به صورت شرطی اعمال کند. آزمایش این حالت به عهده شما می‌باشد. اما به صورت کلی انتظار می‌رود که قدرت توابع High-Order را درک کرده باشید.


Pure Functions

توابع خالص در واقع توابع ریاضی هستند که دو مفهوم ابتدایی که قبلا درباره آن‌ها صحبت کردیم را دنبال می‌کنند؛ شفافیت و صداقت توابع. توابع خالص نباید هیچوقت اثر جانبی (side effect) ای داشته باشند. این یعنی نباید یک global state را تغییر دهند و یا از آن‌ها به عنوان پارامتر ورودی استفاده کنند. توابع خالص به راحتی قابل تست شدن هستند. چون به ازای یک ورودی، یک خروجی ثابت را بر میگردانند. ترتیب محاسبات اهمیتی ندارد! این‌ها بازیگران اصلی یک برنامه تابعی می‌باشد که می‌توانند برای اجرای موازی، محاسبه متاخر ( Lazy Evaluation ) و کش کردن ( memoization ) استفاده شوند.

در ادامه این سری مقالات، به پیاده سازی‌ها و الگوهای رایج برنامه نویسی تابعی با #C بیشتر خواهیم پرداخت.
مطالب
Functional Programming یا برنامه نویسی تابعی - قسمت سوم – Immutability

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

  • Immutability: عدم توانایی تغییر داده
  • State: داده‌هایی که در طول زمان تغییر می‌کنند
  • Side Effect: تغییری که روی داده‌ها اتفاق می‌افتد

در قطعه کد زیر سعی شده‌است تفاوت یک کلاس Stateless و stateful را به سادگی نشان دهیم:

    //Stateful
    public class UserProfile
    {
        private User _user;
        private string _address;

        public void UpdateUser(int userId, string name)
        {
            _user = new User(userId, name);
        }
    }

    //Stateless
    public class User
    {
        public User(int id, string name)
        {
            Id = id;
            Name = name;
        }

        public int Id { get; }
        public string Name { get; }
    }


چرا Immutable بودن مهم است؟ 

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

در واقع انتظار داریم که به ازای یک ورودی بر اساس بدنه‌ی متد، یک خروجی داشته باشیم؛ ولی در واقعیت تاثیری که اجرای متد بر روی state کل کلاس خواهد گذاشت، از دید ما پنهان است و باعث به وجود آمدن مشکلات بعدی خواهد شد. برای مثال قطعه کد بالا را به صورت Honest بازنویسی میکنیم: 

    public class UserProfile
    {
        private readonly User _user;
        private readonly string _address;

        public UserProfile(User user,string address)
        {
            _user = user;
            _address = address;
        }
        public UserProfile UpdateUser(int userId, string name)
        {
            var newUser = new User(userId, name);
            return  new UserProfile(newUser,_address);
        }
    }

    public class User
    {
        public User(int id, string name)
        {
            Id = id;
            Name = name;
        }

        public int Id { get; }
        public string Name { get; }
    }

در این مثال متد UpdateUser به جای  void، یک شی از جنس کلاس UserProfile را بر می‌گرداند. کلاس UserProfile هم برای وهله سازی نیاز به یک شیء از جنس User و Address را دارد. بنابراین مطمئن هستیم که مقدار دهی شده‌اند. نکته دیگر در قطعه کد بالا این است که به ازای هر بار فراخوانی متد، یک شیء جدید بدون وابستگی به وهله سازی اشیاء دیگر، برگردانده میشود.


Immutable بودن باعث می‌شود: 

  • خوانایی کد افزایش پیدا کند
  • جای واحدی برای Validate کردن داشته باشیم
  • به صورت ذاتی Thread Safe باشیم


در مورد محدودیت‌هایی که در کار با اشیاء Immutable باید در نظر داشته باشیم، می‌توان به مصرف بالای رم و سی پی یو، اشاره کرد. در واقع به نسبت حالت mutate، تعداد اشیاء بیشتری ساخته خواهند شد. در فریمورک دات نت برای کار با اشیا immutable امکاناتی در نظر گرفته شده که این هزینه را کاهش می‌دهند. به طور مثال می‌توانیم از کلاس ImmutableList استفاده کنیم و از ایجاد اشیاء اضافه‌تر و تحمیل بار اضافی به GC جلوگیری کنیم. یک مثال: 

//Create Immutable List
ImmutableList<string> list = ImmutableList.Create<string>();
ImmutableList<string> list2 = list.Add("Salam");

//Builder
ImmutableList<string>.Builder builder = ImmutableList.CreateBuilder<string>();
builder.Add("avali");
builder.Add("dovomi");
builder.Add("sevomi");

ImmutableList<string> immutableList = builder.ToImmutable();


چطور با side effect کنار بیایم؟ 

یکی از الگوهای رایج برای این کار، مفهوم جدا سازی Command/Query است. به طور ساده تمامی عملیاتی را که تاثیر گذار هستند، به صورت Command در نظر میگیریم. Command ‌ها معمولا هیچ نوعی را بازگشت نمیدهند و همینطور بر عکس این قضیه برای Query ‌ها صادق است. اشتباه رایج درباره این الگو، محدود کردن این الگو به معماری‌های خاصی مانند Domain Driven می‌باشد؛ در صورتیکه الزامی برای رعایت این الگو در سایر معماری‌ها وجود ندارد. 

به مثال زیر دقت کنید. سعی کردم قسمت‌های Command و Query را از هم جدا کنم: 

در واقع هر برنامه می‌تواند شامل دو قسمت باشد:

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

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

برای مسائلی که در بالا صحبت شد، نمونه‌‌ای را آماده کرده‌ام. این نمونه به طور ساده یک سیستم مدیریت نوبت است که نوبت‌ها را در فایلی ذخیره و بازیابی میکند ( mutate ) و منطق مربوط به نوبت‌ها و زمان ویزیت آن میتواند به صورت immutable پیاده سازی شود. این کد در دو حالت functional و غیر functional پیاده سازی شده تا به خوبی تفاوت آن را در حالت قبل و بعد از برنامه نویسی تابعی بتوانیم درک کنیم. به جهت خوانایی بیشتر و دسترسی به کد‌ها، آن‌ها را روی گیت‌هاب قرار داده و شما میتوانید از اینجا سورس کد مورد نظر را بررسی کنید. سعی شده در این مثال تمامی مواردی که در این قسمت ذکر شد را پیاده سازی کنیم. امیدوارم که مطالب مربوط به برنامه نویسی تابعی یا functional programming توانسته باشد دیدگاه جدیدی را به کدهایی که مینویسیم بدهد. در  قسمت‌های بعدی به مواردی مانند مدیریت exception ‌ها و کار با null ‌ها و ... خواهیم پرداخت.

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

ایجاد متد بیرونی  

در این روش در کلاس استفاده کننده، متدی برای نیاز جدید ساخته می‌شود. به طور مثال به تکه کد زیر توجه نمایید. در این مثال نیاز داریم در شیء نشان دهنده تاریخ، به روز بعد برسیم. برای این منظور می‌توانیم تکه کدی به صورت زیر داشته باشیم:  
DateTime newStart = new DateTime(previousEnd.Year, previousEnd.Month, previousEnd.Day + 1);
زمانیکه بدست آوردن روز بعدی در جاهای زیادی از کد نیاز باشد، باید این روال را در متدی پیاده سازی کرد. این متد می‌تواند بدنه‌ای به صورت زیر داشته باشد:  
private static DateTime NextDay(DateTime now) 
{ 
      return new DateTime(now.Year, now.Month, now.Day + 1); 
}

ایجاد کلاس توسعه دهنده برای کلاس غریبه 

زمانیکه تعداد متدهای جدید مورد نیاز روی یک کلاس غریبه کم است، روش اول روش قابل قبولی است. اما در مواردی تعداد متدهای جدید مورد نیاز زیاد می‌شوند. در چنین شرایطی بهتر است کلاسی را برای توسعه امکانات کلاس غریبه ایجاد کنیم و متدهای مورد نظر را در آن بنویسیم.
برای ایجاد این کلاس نیز دو روش وجود دارد:
روش اول: ارث بری از کلاس غریبه. این روش در شرایطی که کلاس غریبه به صورت sealed باشد، جواب نخواهد داد. به طور مثال تکه کد زیر را در نظر بگیرید. در این تکه کد با فرض این که کد کلاس Person در دست نیست و نیاز است متدی برای دریافت سن فرد، بر اساس سال تولد او به کلاس اضافه شود.  
public class MyPerson : Person 
{ 
    public int GetAge() 
    { 
        return 0; 
    } 
}
یکی از اشکالات این روش این است که برای استفاده از این کلاس و متد تعریف شده در آن، باید تمامی موارد مورد نیاز متد جدید را از کلاس Person، به نوع کلاس جدید تغییر داد. این تغییر در مقاطعی نیاز به Cast یا ایجاد شیء جدیدی با نوع جدیدی منجر خواهد شد.
روش دوم: ساختن wrapper به دور شیء کلاس غریبه. در این روش کلاسی ایجاد می‌شود و شیء‌ای از کلاس غریبه در آن ساخت شده و تمامی متدهای کلاس غریبه در آن تعریف می‌شوند. این متدها صرفا امر هدایت فرخوانی به متدهای کلاس اصلی را انجام می‌دهند. سپس متدهای جدیدی در این کلاس تعریف و پیاده سازی می‌شوند. برای ایجاد امکان محاسبه سن فرد می‌توان کلاسی را مانند کلاس زیر نوشت:  
public class PersonWrapper 
{ 
    private readonly Person _person; 
    public PersonWrapper(Person person) 
    { 
        _person = person; 
    } 
    public int GetAge() 
    { 
        return 0; 
    } 
}
دقت کنید که در این روش پیاده سازی نیز تمامی خصوصیات و متدهای کلاس اصلی در کلاس wrapper وجود خواهند داشت. استفاده از این کلاس به صورت زیر خواهد بود:  
var person = new Person(); 
var wrapper = new PersonWrapper(person); 
wrapper.GetAge();
در زبان برنامه نویسی سی شارپ امکانی به نام extension method وجود دارد که هدف آن پیاده سازی همین طراحی از طریق امکانات زبان است. برای مطالعه بیشتر این مبحث در زبان سی شارپ می‌توانید به اینجا مراجعه نمایید.  
مطالب
الگوی 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 را می‌توان با خیال راحت و بدون نقض هر یک از دستورالعمل‌های شیء گرایی، صرفا به این دلیل که این مکان‌ها به هیچ وجه شیء گرا نیستند، استفاده کرد.
مطالب
آشنایی با CLR: قسمت نهم
net framework. شامل Framework Class Library یا به اختصار FCL است. FCL مجموعه‌ای از dll اسمبلی‌هایی است که صدها و هزاران نوع در آن تعریف شده‌اند و هر نوع تعدادی کار انجام می‌دهد. همچنین مایکروسافت کتابخانه‌های اضافه‌تری را چون azure و Directx نیز ارائه کرده است که باز هر کدام شامل نوع‌های زیادی می‌شوند. این کتابخانه به طور شگفت آوری باعث سرعت و راحتی توسعه دهندگان در زمینه فناوری‌های مایکروسافت گشته است.

تعدادی از فناوری‌هایی که توسط این کتابخانه پشتیبانی می‌شوند در زیر آمده است:

Web Service: این فناوری اجازه‌ی ارسال و دریافت پیام‌های تحت شبکه را به خصوص بر روی اینترنت، فراهم می‌کند و باعث ارتباط جامع‌تر بین برنامه‌ها و فناوری‌های مختلف می‌گردد. در انواع جدیدتر WCF و Web Api نیز به بازار ارائه شده‌اند.

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

Rich Windows GUI Application : برای سهولت در ایجاد برنامه‌های تحت وب حالا چه با فناوری WPF یا فناوری قدیمی و البته منسوخ شده Windows Form.
Windows Console Application: برای ایجاد برنامه‌های ساده و بدون رابط گرافیکی.
Windows Services: شما می‌توانید یک یا چند سرویس تحت ویندوز را که توسط Service Control Manager یا به اختصار SCM کنترل می‌شوند، تولید کنید.
Database stored Procedure: نوشتن stored procedure بر روی دیتابیس‌هایی چون sql server و اوراکل و ... توسط فریم ورک دات نت مهیاست.
Component Libraray: ساخت اسمبلی‌های واحدی که می‌توانند با انواع مختلفی از موارد بالا ارتباط برقرار کنند.
Portable Class Libary : این نوع پروژه‌ها شما را قادر می‌سازد تا کلاس‌هایی با قابلیت انتقال پذیری برای استفاده در سیلور لایت، ویندوز فون و ایکس باکس و فروشگاه ویندوز و ... تولید کنید.

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

در CLR مفهومی به نام Common Type System یا CTS وجود دارد که توضیح می‌دهد نوع‌ها باید چگونه تعریف شوند و چگونه باید رفتار کنند که این قوانین از آنجایی که در ریشه‌ی CLR نهفته است، بین تمامی زبان‌های دات نت مشترک می‌باشد. تعدادی از مشخصات این CTS در زیر آورده شده است ولی در آینده بررسی بیشتری روی آنان خواهیم داشت:

  • فیلد
  • متد
  • پراپرتی
  • رویدادها

CTS همچنین شامل قوانین زیادی در مورد وضعیت کپسوله سازی برای اعضای یک نوع دارد:

  • private
  • public
  • Family یا در زبان‌هایی مثل سی ++ و سی شارپ با نام protected شناخته می‌شود.
  • family and assembly: این هم مثل بالایی است ولی کلاس مشتق شده باید در همان اسمبلی باشد. در زبا‌ن‌هایی چون سی شارپ و ویژوال بیسیک، چنین امکانی پیاده سازی نشده‌است و دسترسی به آن ممکن نیست ولی در IL Assembly چنین قابلیتی وجود دارد.
  • Assembly یا در بعضی زبان‌ها به نام internal شناخته می‌شود.
  • Family Or Assembly: که در سی شارپ با نوع Protected internal شناخته می‌شود. در این وضعیت هر عضوی در هر اسمبلی قابل ارث بری است و یک عضو فقط می‌تواند در همان اسمبلی مورد استفاده قرار بگیرد.

موارد دیگری که تحت قوانین CTS هستند مفاهیم ارث بری، متدهای مجازی، عمر اشیاء و .. است.

یکی دیگر از ویژگی‌های CTS این است که همه‌ی نوع‌ها از نوع شیء Object که در فضای نام system قرار دارد ارث بری کرده‌اند. به همین دلیل همه‌ی نوع‌ها حداقل قابلیت‌هایی را که یک نوع object ارئه میدهد، دارند که به شرح زیر هستند:

  • مقایسه‌ی دو شیء از لحاظ برابری.
  • به دست آوردن هش کد برای هر نمونه از یک شیء
  • ارائه‌ای از وضعیت شیء به صورت رشته ای
  • دریافت نوع شیء جاری
CLS
وجود COM‌ها به دلیل ایجاد اشیاء در یک زبان متفاوت بود تا با زبان دیگر ارتباط برقرار کنند. در طرف دیگر CLR هم بین زبان‌های برنامه نویسی یکپارچگی ایجاد کرده است. یکپارچگی زبان‌های برنامه نویسی علل زیادی دارند. اول اینکه رسیدن به هدف یا یک الگوریتم خاص در زبان دیگر راحت‌تر از زبان پایه پروژه است. دوم در یک کار تیمی که افراد مختلف با دانش متفاوتی حضور دارند و ممکن است زیان هر یک متفاوت باشند.
برای ایجاد این یکپارچگی، مایکروسافت سیستم CLS یا Common Language Specification را راه اندازی کرد. این سیستم برای تولیدکنندگان کامپایلرها جزئیاتی را تعریف می‌کند که کامپایلر آن‌ها را باید با حداقل ویژگی‌های تعریف شده‌ی CLR، پشتیبانی کند.


CLR/CTS مجموعه‌ای از ویژگی‌ها را شامل می‌شود و گفتیم که هر زبانی بسیاری از این ویژگی‌ها را پشتیبانی می‌کند ولی نه کامل. به عنوان مثال برنامه نویسی که قصد کرده از IL Assembly استفاده کند، قادر است از تمامی این ویژگی‌هایی که CLR/CTS ارائه می‌دهند، استفاده کند ولی تعدادی دیگر از زبان‌ها مثل سی شارپ و فورترن و ویژوال بیسیک تنها بخشی از آن را استفاده می‌کنند و CLS حداقل ویژگی که بین همه این زبان‌ها مشترک است را ارائه می‌کند.
شکل زیر را نگاه کنید:

یعنی اگر شما دارید نوع جدیدی را در یک زبان ایجاد می‌کنید که قصد دارید در یک زبان دیگر استفاده شود، نباید از امتیازات ویژه‌ای که آن زبان در اختیار شما می‌گذارد و به بیان بهتر CLS آن‌ها را پشتیبانی نمی‌کند، استفاده کنید؛ چرا که کد شما ممکن است در زبان دیگر مورد استفاده قرار نگیرد.

به کد زیر دقت کنید. تعدادی از کدها سازگاری کامل با CLS دارند که به آن‌ها CLS Compliant گویند و تعدادی از آن‌ها non-CLS-Compliant هستند یعنی با CLS سازگاری ندارند ولی استفاده از خاصیت [(assembly: CLSCompliant(true]  باعث می‌شود که تا کامپایلر از پشتیبانی و سازگاری این کدها اطمینان کسب کند و در صورت وجود، از اجرای آن جلوگیری کند. با کمپایل کد زیر دو اخطار به ما میرسد.
using System;

// Tell compiler to check for CLS compliance
[assembly: CLSCompliant(true)]

namespace SomeLibrary {

// Warnings appear because the class is public
public sealed class SomeLibraryType {

// Warning: Return type of 'SomeLibrary.SomeLibraryType.Abc()'
// is not CLS­compliant
public UInt32 Abc() { return 0; }

// Warning: Identifier 'SomeLibrary.SomeLibraryType.abc()'
// differing only in case is not CLS­compliant
public void abc() { }

// No warning: this method is private
private UInt32 ABC() { return 0; }
}
}

اولین اخطار اینکه یکی از متدها یک عدد صحیح بدون علامت unsigned integer را بر می‌گرداند که همه‌ی زبان‌ها آن را پشتیبانی نمی‌کنند و خاص بعضی از زبان هاست.
دومین اخطار اینکه دو متد یکسان وجود دارند که در حروف بزرگ و کوچک تفاوت دارند. ولی زبان هایی چون ویژوال بیسیک نمی‌توانند تفاوتی بین دو متد abc و ABC بیابند.

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

سازگاری با کدهای مدیریت نشده
در بالا در مورد یکپارچگی و سازگاری کدهای مدیریت شده توسط CLS صحبت کردیم ولی در مورد ارتباط با کدهای مدیریت نشده چطور؟
مایکروسافت موقعیکه CLR را ارئه کرد، متوجه این قضیه بود که بسیاری از شرکت‌ها توانایی اینکه کدهای خودشون را مجددا طراحی و پیاده سازی کنند، ندارند و خوب، سورس‌های مدیریت نشده‌ی زیادی هم موجود هست که توسعه دهندگان علاقه زیادی به استفاده از آن‌ها دارند. در نتیجه مایکروسافت طرحی را ریخت که CLR هر دو قسمت کدهای مدیریت شده و نشده را پشتیبانی کند. دو نمونه از این پشتیبانی را در زیر بیان می‌کنیم:
یک. کدهای مدیریت شده می‌توانند توابع مدیریت شده را در قالب یک dll صدا زده و از آن‌ها استفاده کنند.
دو. کدهای مدیریت شده می‌توانند از کامپوننت‌های COM استفاده کنند: بسیاری از شرکت‌ها از قبل بسیاری از کامپوننت‌های COM را ایجاد کرده بودند که کدهای مدیریت شده با راحتی با آن‌ها ارتباط برقرار می‌کنند. ولی اگر دوست دارید روی آن‌ها کنترل بیشتری داشته باشید و آن کدها را به معادل CLR تبدیل کنید؛ میتوانید از ابزار کمکی که مایکروسافت همراه فریم ورک دات نت ارائه کرده است استفاده کنید. نام این ابزار TLBIMP.exe می‌باشد که از Type Library Importer گرفته شده است.

سه. اگر کدهای مدیریت نشده‌ی زیادتری دارید شاید راحت‌تر باشد که برعکس کار کنید و کدهای مدیریت شده را در در یک برنامه‌ی مدیریت نشده اجرا کنید. این کدها می‌توانند برای مثال به یک Activex یا shell Extension تبدیل شده و مورد استفاده قرار گیرند. ابزارهای TLBEXP .exe و RegAsm .exe برای این منظور به همراه فریم ورک دات نت عرضه شده اند.
سورس کد Type Library Importer را میتوانید در کدپلکس بیابید.
در ویندوز 8 به بعد مایکروسافت API جدید را تحت عنوان WinsowsRuntime یا winRT ارائه کرده است . این api یک سیستم داخلی را  از طریق کامپوننت‌های com ایجاد کرده و به جای استفاده از فایل‌های کتابخانه‌ای، کامپوننت‌ها api هایشان را از طریق متادیتاهایی بر اساس استاندارد ECMA که توسط تیم دات نت طراحی شده است معرفی می‌کنند.
زیبایی این روش اینست که کد نوشته شده در زبان‌های دات نت  می‌تواند به طور مداوم با api‌های winrt ارتباط برقرار کند. یعنی همه‌ی کارها توسط CLR انجام می‌گیرد بدون اینکه لازم باشد از ابزار اضافی استفاده کنید. در آینده در مورد winRT بیشتر صحبت می‌کنیم.
 
سخن پایانی: ممنون از دوستان عزیز بابت پیگیری مطالب تا بدینجا. تا این قسمت فصل اول کتاب با عنوان اصول اولیه CLR بخش اول مدل اجرای CLR به پایان رسید.
ادامه‌ی مطالب بعد از تکمیل هر بخش در دسترس دوستان قرار خواهد گرفت.
مطالب
مدیریت پیشرفته‌ی حالت در React با Redux و Mobx - قسمت اول - Redux چیست؟
Redux و Mobx، کتابخانه‌های کمکی هستند برای مدیریت حالت برنامه‌های پیچیده‌ی React. هرچند React به صورت توکار به همراه امکانات مدیریت حالت است، اما این کتابخانه‌ها مزایای ویژه‌ای را به آن اضافه می‌کنند. در این سری ابتدا کتابخانه‌ی Redux را به صورت خالص و مجزای از React بررسی می‌کنیم. از این کتابخانه در برنامه‌های Angular و Ember هم می‌توان استفاده کرد و به صورت اختصاصی برای React طراحی نشده‌است. سپس آن‌را به برنامه‌های React متصل می‌کنیم. در آخر کتابخانه‌ی محبوب دیگری را به نام Mobx بررسی می‌کنیم که برای مدیریت حالت، اصول برنامه نویسی شیءگرا و همچنین Reactive را با هم ترکیب می‌کند و این روزها در برنامه‌های React، بیشتر از Redux مورد استفاده قرار می‌گیرد.


چرا به ابزارهای مدیریت حالت نیاز داریم؟

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


داشتن تنها یک محل برای ذخیره سازی state در برنامه

همانطور که در قسمت 8 ترکیب کامپوننت‌ها در سری React 16x بررسی کردیم، هر کامپوننت در React، دارای state خاص خودش است و این state از سایر کامپوننت‌ها کاملا مستقل و ایزوله‌است. این مورد با بزرگ‌تر شدن برنامه و برقراری ارتباط بین کامپوننت‌ها، مشکل ایجاد می‌کند. برای مثال اگر بخواهیم دکمه‌ای را در صفحه قرار داده و توسط این دکمه درخواست صفر شدن مقدار هر کدام از شمارشگرها را صادر کنیم، با صفر کردن value هر کدام از این کامپوننت‌ها، اتفاقی رخ نمی‌دهد. چون state محلی این کامپوننت‌ها، با سایر اجزای صفحه به اشتراک گذاشته نمی‌شود و باید آن‌را تبدیل به یک controlled component کرد، بطوریکه دارای local state خاص خودش نیست و تمام داده‌های دریافتی را از طریق this.props دریافت می‌کند و هر زمانیکه قرار است داده‌ای تغییر کند، رخ‌دادی را به والد خود صادر می‌کند. بنابراین این کامپوننت به طور کامل توسط والد آن کنترل می‌شود. تازه این روش در مورد کامپوننت‌هایی صدق می‌کند که رابطه‌ی والد و فرزندی بین آن‌ها وجود دارد. اگر چنین رابطه‌ای وجود نداشت، باید state را به یک سطح بالاتر انتقال داد. برای مثال باید state کامپوننت Counters را به والد آن که کامپوننت App است، منتقل کرد. پس از آن چون کامپوننت‌های ما، از کامپوننت App مشتق می‌شوند، اکنون می‌توان این state را به تمام فرزندان App توسط props منتقل کرد و به اشتراک گذاشت. این مورد هم مانند مثال انتقال اطلاعات کاربر لاگین شده‌ی به سیستم، به تمام زیر قسمت‌های برنامه، نیاز به ارسال اطلاعات از طریق props یک کامپوننت، به کامپوننت بعدی را دارد و به همین ترتیب برای مابقی که به props drilling مشهور است و روش پسندیده‌ای نیست.


Redux چیست؟ ذخیره سازی کل درخت state یک برنامه، در یک محل. به این ترتیب به یک شیء جاوا اسکریپتی بزرگ خواهیم رسید که در برگیرنده‌ی تمام state برنامه‌است. یکی از مزایای آن امکان serialize و deserialize کل این شیء، به سادگی است. برای مثال توسط متد JSON.stringify می‌توان آن‌را در جائی ذخیره کرد و سپس آن‌را به صورت یک شیء جاو اسکریپتی در زمانی دیگر بازیابی کرد. یکی از مزایای آن، امکان بازیابی دقیق شرایط کاربری است که دچار مشکل شده‌است و سپس دیباگ و رفع مشکل او، در زمانی دیگر.


تاریخچه‌ای از سیستم‌های مدیریت حالت

همه چیز با AngularJS 1x شروع شد که از data binding دو طرفه پشتیبانی می‌کرد. هرچند این روش برای همگام نگه داشتن View و مدل برنامه، مفید است، اما در Viewهای پیچیده، برنامه را کند می‌کند. در همین زمان فیس‌بوک، روش مدیریت حالتی را به نام Flux ارائه داد که از data binding یک طرفه پشتیبانی می‌کرد. به این معنا که در این روش، همواره اطلاعات از View به مدل، جریان پیدا می‌کند. کار کردن با آن ساده‌است؛ چون نیازی نیست حدس زده شود که اکنون جریان اطلاعات از کدام سمت است. اما مشکل آن عدم هماهنگی model و view، در بعضی از حالات است. Flux از این جهت به وجود آمد که مدیریت حالت در برنامه‌های React آن زمان، پیچیده بود و مقیاس پذیری کمی داشت (پیش از ارائه‌ی Context و Hooks). در کل Flux صرفا یکسری الگوی مدیریت حالت را بیان می‌کند و یک کتابخانه‌ی مجزا نیست. بر مبنای این الگوها و قراردادها، می‌توان کتابخانه‌های مختلفی را ایجاد کرد. از این رو در سال 2015، کتابخانه‌های زیادی مانند Reflux, Flummox, MartyJS, Alt, Redux و غیره برای پیاده سازی آن پدید آمدند. در این بین، کتابخانه‌ی Redux ماندگار شد و پیروز این نبرد بود!


توابع خالص و ناخالص (Pure & Impure Functions)

پیش از شروع بحث، نیاز است با یک‌سری از واژه‌ها مانند توابع خالص و ناخالص آشنا شد. این نکات از این جهت مهم هستند که Redux فقط با توابع خالص کار می‌کند.
توابع خالص: تعدادی آرگومان را دریافت کرده و بر اساس آن‌ها، مقداری را باز می‌گردانند.
// Pure
const add = (a, b) => {
  return a + b;
}
در اینجا یک تابع خالص را مشاهده می‌کنید که a و b را دریافت کرده و بر این اساس، یک خروجی کاملا مشخص را بازگشت می‌دهد.

توابع ناخالص: این نوع توابع سبب تغییراتی در متغیرهایی خارج از میدان دید خود می‌شوند و یا به همراه یک سری اثرات جانبی (side effects) مانند تعامل با دنیای خارج (وجود یک console.log در آن تابع و یا دریافت اطلاعاتی از یک API خارجی) هستند.
// Impure
const b;

const add = (a) => {
  return a + b;
}
تابع تعریف شده‌ی در اینجا ناخالص است؛ چون با اطلاعاتی خارج از میدان دید خود مانند متغیر b، تعامل دارد. این تعامل با دنیای خارج، حتی در حد نوشتن یک console.log:
// Impure
const add = (a, b) => {
  console.log('lolololol');
  return a + b;
}
یک تابع خالص را تبدیل به یک تابع ناخالص می‌کند و یا نمونه‌ی دیگر این تعاملات، فراخوانی سرویس‌های backend در برنامه هستند که یک تابع را ناخالص می‌کنند:
// Impure
const add = (a, b) => {
   Api.post('/add', { a, b }, (response) => {
    // Do something.
   });
};


روش‌هایی برای جلوگیری از تغییرات در اشیاء در جاوا اسکریپت

ایجاد تغییرات در آرایه‌ها و اشیاء (Mutating arrays and objects) نیز ناخالصی ایجاد می‌کند؛ از این جهت که سبب تغییراتی در دنیای خارج (خارج از میدان دید تابع) می‌شویم. به همین جهت نیاز به روش‌هایی وجود دارد که از این نوع تغییرات جلوگیری کرد:
// Copy object
const original = { a: 1, b: 2 };
const copy = Object.assign({}, original);
برای تغییری در یک شیء، تنها کافی است خاصیتی را به آن اضافه کنیم و یا با استفاده از واژه‌ی کلیدی delete، خاصیتی را از آن حذف کنیم. به همین جهت برای اینکه تغییرات ما بر روی شیء اصلی اثری را باقی نگذارند، یکی از روش‌ها، استفاده از متد Object.assign است. کار آن، یکی کردن اشیایی است که به آن ارسال می‌شوند. به همین جهت در اینجا با یک شیء خالی، از صفر شروع می‌کنیم. سپس دومین آرگومان آن را به همان شیء مدنظر، تنظیم می‌کنیم. به این ترتیب به یک کپی از شیء اصلی می‌رسیم که دیگر به آن، اتصالی را ندارد. به همین جهت اگر بر روی این شیء کپی تغییراتی را ایجاد کنیم، به شیء اصلی کپی نمی‌شود و سبب تغییرات در آن (mutation) نخواهد شد.
برای مثال در React، برای انجام رندر نهایی، در پشت صحنه کار مقایسه‌ی اشیاء صورت می‌گیرد. به همین جهت اگر همان شیءای را که ردیابی می‌کند تغییر دهیم، دیگر نمی‌تواند به صورت مؤثری فقط قسمت‌های تغییر کرده‌ی آن‌را تشخیص داده و کار رندر را فقط بر اساس آن‌ها انجام دهد و مجبور خواهد شد کل یک شیء را بارها و بارها رندر کند که اصلا بهینه نیست. به همین جهت، ایجاد تغییرات مستقیم در شیءای که به state آن انتساب داده می‌شود، مجاز نیست.

متد Object.assign، چندین شیء را نیز می‌تواند با هم یکی کند و شیء جدیدی را تشکیل دهد:
// Extend object
const original = { a: 1, b: 2 };
const extension = { c: 3 };
const extended = Object.assign({}, original, extension);
روش دیگر ایجاد یک کپی و یا clone از یک شیء را که پیشتر در سری «React 16x» بررسی کردیم، به کمک امکانات ES-6، به صورت زیر است:
// Copy object
const original = { a: 1, b: 2 };
const copy = { ...original };
در اینجا نیز ابتدا یک شیء خالی را ایجاد می‌کنیم و سپس توسط spread operator، خواص شیء قبلی را درون آن باز کرده و قرار می‌دهیم. به این ترتیب به یک clone از شیء اصلی می‌رسیم. این حالت نیز از ترکیب چندین شیء با هم، پشتیبانی می‌کند:
// Extend object
const original = { a: 1, b: 2 };
const extension = { c: 3 };
const extended = { ...original, ...extension };


روش‌هایی برای جلوگیری از تغییرات در آرایه‌ها در جاوا اسکریپت

متد slice آرایه‌ها نیز بدون ذکر آرگومانی، یک کپی از آرایه‌ی اصلی را ایجاد می‌کند:
// Copy array
const original = [1, 2, 3];
const copy = [1, 2, 3].slice();
همچنین معادل همین قطعه کد در ES-6 به همراه spread operator به صورت زیر است:
// Copy array
const original = [1, 2, 3];
const copy = [ ...original ];
و یا اگر بخواهیم یک کپی از چندین آرایه را ایجاد کنیم می‌توان از متد concat استفاده کرد:
// Extend array
const original = [1, 2, 3];
const extended = original.concat(4);
const moreExtended = original.concat([4, 5]);
متد Array.push، هرچند سبب افزوده شدن عنصری به یک آرایه می‌شود، اما یک mutation را نیز ایجاد می‌کند؛ یعنی تغییرات آن به دنیای خارج اعمال می‌گردد. اما Array.concat یک آرایه‌ی کاملا جدید را ایجاد می‌کند و همچنین امکان ترکیب آرایه‌ها را نیز به همراه دارد.
معادل قطعه کد فوق در ES-6 و به همراه spread operator آن به صورت زیر است:
// Extend array
const original = [1, 2, 3];
const extended = [ ...original, 4 ];
const moreExtended = [ ...original, ...extended, 5 ];


مفاهیم ابتدایی Redux


در Redux برای ایجاد تغییرات در شیء کلی state، از مفهومی به نام dispatch actions استفاده می‌شود. action در اینجا به معنای رخ‌دادن چیزی است؛ مانند کلیک بر روی یک دکمه و یا دریافت اطلاعاتی از یک API. در این حالت مقایسه‌ای بین وضعیت قبلی state و وضعیت فعلی آن صورت می‌گیرد و تغییرات مورد نیاز جهت اعمال به UI، محاسبه خواهند شد.
اصلی‌ترین جزء Redux، تابعی است به نام Reducer. این تابع، یک تابع خالص است و دو آرگومان را دریافت می‌کند:


تابع Reducer، بر اساس action و یا رخ‌دادی، ابتدا کل state برنامه را دریافت می‌کند و سپس خروجی آن بر اساس منطق این تابع، یک state جدید خواهد بود. اکنون که این state جدید را داریم، برنامه‌ی React ما می‌تواند به تغییرات آن گوش فرا داده و بر اساس آن، UI را به روز رسانی کند. به این ترتیب کار اصلی مدیریت state، به خارج از برنامه‌ی React منتقل می‌شود.

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


مزایای کار با Redux

- داشتن یک مکان مرکزی برای ذخیره سازی کلی حالت برنامه (به آن «source of truth» و یا store هم گفته می‌شود): به این ترتیب مشکل ارسال خواص در بین کامپوننت‌های عمیق و چند سطحی، برطرف شده و هر زمانیکه نیاز بود، از آن اطلاعاتی را دریافت و یا با قالب خاصی، آن‌را به روز رسانی می‌کنند.
- رسیدن به به‌روز رسانی‌های قابل پیش بینی state: هرچند در حالت کار با Redux، یک شیء بزرگ جاوا اسکریپتی، کل state برنامه را تشکیل می‌دهد، اما امکان کار مستقیم با آن و تغییرش وجود ندارد. به همین جهت است که برای کار با آن، باید رویدادی را از طریق actionها به تابع Reducer آن تحویل داد. چون Reducer یک تابع خالص است، با دریافت یک سری ورودی مشخص، همواره یک خروجی مشخص را نیز تولید می‌کند. به همین جهت قابلیت ضبط و تکرار را پیدا می‌کند؛ همان بحث serialize و deseriliaze، توسط ابزاری مانند: logrocket. به علاوه قابلیت undo و redo را نیز می‌توان به این ترتیب پیاده سازی کرد (state جدید محاسبه شده، مشخص است، کل state قبلی را نیز داریم یا می‌توان ذخیره کرد و سپس برای undo، آن‌را جایگزین state جدید نمود). افزونه‌ی redux dev tools نیز قابلیت import و export کل state را به همراه دارد.
- چون تابع Reducer، یک تابع خالص است و همواره خروجی‌های مشخصی را به ازای ورودی‌های مشخصی، تولید می‌کند، آزمایش کردن، پیاده سازی و حتی logging آن نیز ساده‌تر است. در این بین حتی یک افزونه‌ی مخصوص نیز برای دیباگ آن تهیه شده‌است: redux-devtools-extension. تابع خالص، تابعی است که به همراه اثرات جانبی نیست (side effects)؛ به همین جهت عملکرد آن کاملا قابل پیش بینی بوده و آزمون پذیری آن به دلیل نداشتن وابستگی‌های خارجی، بسیار بالا است.


Context API خود React چطور؟

در قسمت 33 سری React 16x، مفهوم React Context را بررسی کردیم. پس از معرفی آن با React 16.3، مقالات زیادی منتشر شدند که ... Redux مرده‌است (!) و یا بجای Redux از React context استفاده کنید. اما واقعیت این است که React Redux در پشت صحنه از React context استفاده می‌کند و تابع connect آن دقیقا به همین زیر ساخت متصل می‌شود.
کار با Redux مزایایی مانند کارآیی بالاتر، با کاهش رندر‌های مجدد کامپوننت‌ها، دیباگ ساده‌تر با افزونه‌های اختصاصی و همچنین سفارشی سازی، مانند نوشتن میان‌افزارها را به همراه دارد. اما شاید واقعا نیازی به تمام این امکانات را هم نداشته باشید؛ اگر هدف، صرفا انتقال ساده‌تر اطلاعات بوده و برنامه‌ی مدنظر نیز کوچک است. React Context برخلاف Redux، نگهدارنده‌ی state نیست و بیشتر هدفش محلی برای ذخیره سازی اطلاعات مورد استفاده‌ی در چندین و چند کامپوننت تو در تو است. هرچند شبیه به Redux می‌توان اشاره‌گرهایی از متدها را به استفاده کنندگان از آن ارسال کرد تا سبب بروز رویدادها و اکشن‌هایی در کامپوننت تامین کننده‌ی Contrext شوند (یا یک کتابخانه‌ی ابتدایی شبیه به Redux را توسط آن تهیه کرد). بنابراین برای انتخاب بین React Context و Redux باید به اندازه‌ی برنامه، تعداد نفرات تیم، آشنایی آن‌ها با مفاهیم Redux دقت داشت.
مطالب
مقدمه‌ای بر تزریق وابستگی‌ها درASP.NET Core
ASP.NET Core با ذهنیت پشتیبانی و استفاده از تزریق وابستگی‌ها ایجاد شده‌است. اپلیکیشن‌های ASP.NET Core از سرویس‌های ذاتی فریم ورک که داخل متدهای کلاس Startup پروژه تزریق شده‌اند و همچنین سرویس‌های اپلیکیشن که تنظیمات خاص آنها در پروژه انجام گرفته است، استفاده می‌کنند. سرویس کانتینر پیش فرض ارائه شده توسط ASP.NET Core، مجموعه‌ای حداقلی از ویژگی‌ها را ارائه می‌کند و هدف آن جایگزینی با دیگر فریم ورک‌های تزریق وابستگی نمی‌باشد.

مشاهده یا دانلود کدهای مقاله


تزریق وابستگی چیست؟

تزریق وابستگی (DI) تکنیکی برای دستیابی به اتصال شل بین اشیاء و همکاران اشیاء و وابستگی‌های بین آنها می‌باشد. یک شیء برای انجام وظایف خود، بجای اینکه اشیاء همکار خود را به صورت مستقیم نمونه سازی کند، یا از ارجاعات استاتیک استفاده نماید، می‌تواند از اشیائی که برایش تامین شده‌است، استفاده کند. در اغلب موارد کلاس‌ها، وابستگی‌های خود را از طریق سازنده‌ی خود درخواست می‌کنند، که به آنها اجازه می‌دهد اصل وابستگی صریح را رعایت کنند (Explicit Dependencies Principle). این روش را «تزریق در سازنده» می‌نامند.
از آنجا که در طراحی کلاس‌ها با استفاده از DI، نمونه سازی مستقیم، توسط کلاس‌ها و به صورت Hard-coded انجام نمی‌گیرد، وابستگی بین اشیاء کم شده و پروژه‌ای با اتصالات شل به دست می‌آید. با این کار اصل وابستگی معکوس (Dependency Inversion Principle) رعایت می‌شود. بر اساس این اصل، ماژول‌های سطح بالا نباید به ماژول‌های سطح پایین خود وابسته باشند؛ بلکه هر دو باید به کلاس‌هایی انتزاعی وابسته باشند. اشیاء بجای ارجاع به پیاده سازی‌های خاص کلاس‌های همکار خود، کلاس‌های انتزاعی، معمولاٌ اینترفیس آنها را درخواست می‌کنند و هنگام نمونه سازی از آنها (داخل متد سازنده) کلاس پیاده سازی شده برایشان تامین می‌شود. خارج کردن وابستگی‌‎های مستقیم از کلاس‌ها و تامین پیاده سازی‌های این اینترفیس‌ها به صورت پارامتر‌هایی برای کلاس‌ها، یک مثال از الگوی طراحی استراتژی (Strategy design pattern) می‌باشد.

در حالتیکه کلاس‌ها به تعداد زیادی کلاس وابستگی داشته باشند و برای اجرا شدن، نیاز به تامین وابستگی‌هایشان داشته باشند، بهتر است یک کلاس اختصاصی، برای نمونه سازی این کلاس‌ها با وابستگی‌های مورد نیاز آنها، در سیستم وجود داشته باشد. این کلاس نمونه ساز را کانتینرIoC، یا کانتینر DI یا به طور خلاصه کانتینر می‌نامند ( Inversion of Control (IoC) ). کانتینر در اصل یک کارخانه می‌باشد که وظیفه‌ی تامین نمونه‌هایی از کلاس‌هایی را که از آن درخواست می‌شود، انجام می‌دهد. اگر یک کلاس تعریف شده، وابستگی به کلاس‌های دیگر داشته باشد و کانتینر برای ارائه وابستگی‌های کلاس تعریف شده تنظیم شده باشد، هر موقع نیاز به یک نمونه از این کلاس وجود داشته باشد، به عنوان بخشی از کار نمونه سازی از کلاس مورد نظر، کلاس‌های وابسته‌ی آن نیز ایجاد می‌شوند (همه‌ی کارهای مربوط به نمونه سازی کلاس خاص و کلاس‌های وابسته به آن توسط کانتینر انجام می‌گیرد). به این ترتیب، می‌توان وابستگی‌های بسیار پیچیده و تو در توی موجود در سیستم را بدون نیاز به هیچگونه نمونه سازی hard-code شده، برای کلاس‌ها فراهم کرد. کانتینرها علاوه بر ایجاد اشیاء و وابستگی‌های موجود در آنها، معمولا طول عمر اشیاء در اپلیکیشن را نیز مدیریت می‌کنند.
ASP.NET Core یک کانتینر بسیار ساده را به نام اینترفیس IServiceProvider  ارائه داده است که به صورت پیش فرض از تزریق وابستگی در سازنده‌ی کلاس‌ها پشتیبانی می‌کند و همچنین ASP.NET برخی از سرویس‌های خود را از طریق DI در دسترس قرار داده است. کانتینرASP.NET، یک اشاره‌گر به کلاس‌هایی است که به عنوان سرویس عمل می‌کنند. در ادامه‌ی این مقاله، سرویس‌ها به کلاس‌هایی گفته می‌شود که به وسیله‌ی کانتینر ASP.NET Core مدیریت می‌شوند. شما می‌توانید سرویس ConfigureServices کانتینر را در داخل کلاس Startup پروژه خود پیکربندی کنید.


تزریق وابستگی از طریق متد سازنده‌ی کلاس

تزریق وابستگی از طریق متد سازنده، مستلزم آن است که سازنده‌ی کلاس مورد نظر عمومی باشد. در غیر این صورت، اپلیکیشن شما استثنای InvalidOperationException  را با پیام زیر نشان می‌دهد:
 A suitable constructor for type 'YourType' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor.

تزریق از طریق متد سازنده مستلزم آن است که تنها یک سازنده‌ی مناسب وجود داشته باشد. البته Overload سازنده امکان پذیر است؛ ولی باید تنها یک متد سازنده وجود داشته باشد که آرگومان‌های آن توسط DI قابل ارائه باشند. اگر بیش از یکی وجود داشته باشد، سیستم استثنای InvalidOperationException را با پیام زیر نشان می‌دهد:
 Multiple constructors accepting all given argument types have been found in type 'YourType'. There should only be one applicable constructor.

سازندگان می‌توانند آرگومان‌هایی را از طریق DI دریافت کنند. برای این منظور آرگومان‌های این سازنده‌ها باید مقدار پیش فرضی را داشته باشند. به مثال زیر توجه نمایید:
// throws InvalidOperationException: Unable to resolve service for type 'System.String'...
public CharactersController(ICharacterRepository characterRepository, string title)
{
    _characterRepository = characterRepository;
    _title = title;
}

// runs without error
public CharactersController(ICharacterRepository characterRepository, string title = "Characters")
{
    _characterRepository = characterRepository;
    _title = title;
}


استفاده از سرویس ارائه شده توسط فریم ورک

متد ConfigureServices در کلاس Startup، مسئول تعریف سرویس‌هایی است که سیستم از آن استفاده می‌کند. از جمله‌ی این سرویس‌ها می‌توان به ویژگی‌های پلتفرم مانند EF Core و ASP.NET Core MVC اشاره کرد. IServiceCollection که به ConfigureServices ارائه می‌شود، سرویس‌های زیر را تعریف می‌کند (که البته بستگی به نوع پیکربندی هاست دارد):

  نوع سرویس    طول زندگی 
    Microsoft.AspNetCore.Hosting.IHostingEnvironment  
 Singleton 
    Microsoft.AspNetCore.Hosting.IApplicationLifetime     Singleton 
    Microsoft.AspNetCore.Hosting.IStartup     Singleton 
    Microsoft.AspNetCore.Hosting.Server.IServer     Singleton 
    Microsoft.Extensions.Options.IConfigureOptions     Transient 
    Microsoft.Extensions.ObjectPool.ObjectPoolProvider     Singleton 
    Microsoft.AspNetCore.Hosting.IStartupFilter     Transient 
    System.Diagnostics.DiagnosticListener     Singleton 
    System.Diagnostics.DiagnosticSource     Singleton 
    Microsoft.Extensions.Options.IOptions     Singleton 
    Microsoft.AspNetCore.Http.IHttpContextFactory     Transient 
    Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory     Transient 
    Microsoft.Extensions.Logging.ILogger     Singleton 
    Microsoft.Extensions.Logging.ILoggerFactory  
 Singleton 

در زیر نمونه ای از نحوه‌ی اضافه کردن سرویس‌های مختلف را به کانتینر، با استفاده از متدهای الحاقی مانند AddDbContext، AddIdentity و AddMvc، مشاهده می‌کنید:

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

    services.AddMvc();

    // Add application services.
    services.AddTransient<IEmailSender, AuthMessageSender>();
    services.AddTransient<ISmsSender, AuthMessageSender>();
}
ویژگی‌ها و میان افزار‌های ارائه شده توسط ASP.NET، مانند MVC، از یک قرارداد، با استفاده از متد الحاقی AddServiceName برای ثبت تمام سرویس‌های مورد نیاز این ویژگی پیروی می‌کنند.


ثبت سرویس‌های اختصاصی

شما می‌توانید سرویس‌های اپلیکیشن خودتان را به ترتیبی که در تکه کد زیر مشاهده می‌کنید، ثبت نمایید. اولین نوع جنریک، نوعی است که از کانتینر درخواست خواهد شد و معمولا به شکل اینترفیس می‌باشد. نوع دوم، نوع پیاده سازی شده‌ای است که به وسیله‌ی کانتینر، نمونه سازی خواهد شد و کانتینر برای درخواست‌های از نوع اول، این نمونه از  تایپ را ارائه خواهد کرد:
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();

نکته:
هر متد الحاقی <services.Add<ServiceName، سرویس‌هایی را اضافه و پیکربندی می‌کند. به عنوان مثال services.AddMvc نیازمندی‌های سرویس MVC را اضافه می‌کند. توصیه می‌شود شما هم با افزودن متدهای الحاقی در فضای نام Microsoft.Extensions.DependencyInjection این قرارداد را رعایت نمائید. این کار باعث کپسوله شدن ثبت گروهی سرویس‌ها می‌شود.
متد AddTransient، برای نگاشت نوع‌های انتزاعی به سرویس‌های واقعی که نیاز به نمونه سازی به ازای هر درخواست دارند، استفاده می‌شود. در اصطلاح، طول عمر سرویس‌ها در اینجا مشخص می‌شوند. در ادامه گزینه‌های دیگری هم برای طول عمر سرویس‌ها تعریف خواهند شد. خیلی مهم است که برای هر یک از سرویس‌های ثبت شده، طول عمر مناسبی را انتخاب نمایید. آیا برای هر کلاس که سرویسی را درخواست می‌کند، باید یک نمونه‌ی جدید ساخته شود؟ آیا فقط یک نمونه در طول یک درخواست وب مورد استفاده قرار می‌گیرد؟ یا باید از یک نمونه‌ی واحد برای طول عمر کل اپلیکیشن استفاده شود؟
در مثال ارائه شده‌ی در این مقاله، یک کنترلر ساده به نام CharactersController وجود دارد که نام کاراکتری را نشان می‌دهد. متد Index، لیست کنونی کاراکترهایی را که در اپلیکیشن ذخیره شده‌اند، نشان می‌دهد. در صورتیکه این لیست خالی باشد، تعدادی به آن اضافه می‌کند. توجه داشته باشید، اگرچه این اپلیکیشن از Entity Framework Core و ClassDataContext برای داده‌های مانا استفاده می‌کند، هیچیکدام از آنها در کنترلر ظاهر نمی‌شوند. در عوض، مکانیزم دسترسی به داده‌های خاص، در پشت یک اینترفیس (ICharacterRepository) مخفی شده است (طبق الگوی طراحی ریپازیتوری). یک نمونه از ICharacterRepository از طریق سازنده درخواست می‌شود و به یک فیلد خصوصی اختصاص داده می‌شود، سپس برای دسترسی به کاراکتر‌ها در صورت لزوم استفاده می‌شود:
public class CharactersController : Controller
{
    private readonly ICharacterRepository _characterRepository;

    public CharactersController(ICharacterRepository characterRepository)
    {
        _characterRepository = characterRepository;
    }

    // GET: /characters/
    public IActionResult Index()
    {
        PopulateCharactersIfNoneExist();
        var characters = _characterRepository.ListAll();

        return View(characters);
    }

    private void PopulateCharactersIfNoneExist()
    {
        if (!_characterRepository.ListAll().Any())
        {
            _characterRepository.Add(new Character("Darth Maul"));
            _characterRepository.Add(new Character("Darth Vader"));
            _characterRepository.Add(new Character("Yoda"));
            _characterRepository.Add(new Character("Mace Windu"));
        }
    }
}

ICharacterRepository دو متد مورد نیاز کنترلر برای کار با نمونه‌های Character را تعریف می‌کند:
using System.Collections.Generic;
using DependencyInjectionSample.Models;

namespace DependencyInjectionSample.Interfaces
{
    public interface ICharacterRepository
    {
        IEnumerable<Character> ListAll();
        void Add(Character character);
    }
}
این اینترفیس با نوع واقعی CharacterRepository پیاده سازی شده است که در زمان اجرا استفاده می‌شود:

using System.Collections.Generic;
using System.Linq;
using DependencyInjectionSample.Interfaces;

namespace DependencyInjectionSample.Models
{
    public class CharacterRepository : ICharacterRepository
    {
        private readonly ApplicationDbContext _dbContext;

        public CharacterRepository(ApplicationDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        public IEnumerable<Character> ListAll()
        {
            return _dbContext.Characters.AsEnumerable();
        }

        public void Add(Character character)
        {
            _dbContext.Characters.Add(character);
            _dbContext.SaveChanges();
        }
    }
}
توجه داشته باشید که CharacterRepository یک ApplicationDbContext را در سازنده‌ی خود درخواست می‌کند. همانطور که مشاهده می‌شود هر وابستگی درخواست شده، به نوبه خود وابستگی‌های دیگری را درخواست می‌کند. تزریق وابستگی‌هایی به شکل زنجیره‌ای، همانند این مثال غیر معمول نیست. کانتینر مسئول resolve (نمونه سازی) همه‌ی وابستگی‌های موجود در گراف وابستگی و بازگرداندن سرویس کاملا resolve شده می‌باشد.

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

در مورد مثال مطرح شده، ICharacterRepository و به نوبه خود ApplicationDbContext باید با سرویس‌های خود در کانتینر ConfigureServices و کلاس Startup ثبت شوند. ApplicationDbContext با فراخوانی متد <AddDbContext<T پیکربندی می‌شود. کد زیر ثبت کردن نوع CharacterRepository را نشان می‌دهد:
public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseInMemoryDatabase()
    );

    // Add framework services.
    services.AddMvc();

    // Register application services.
    services.AddScoped<ICharacterRepository, CharacterRepository>();
    services.AddTransient<IOperationTransient, Operation>();
    services.AddScoped<IOperationScoped, Operation>();
    services.AddSingleton<IOperationSingleton, Operation>();
    services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty));
    services.AddTransient<OperationService, OperationService>();
}
کانتکست انتیتی فریم ورک، با استفاده از متدهای کمکی که در تکه کد بالا نشان داده شده است، باید با طول عمر Scoped به کانتینر سرویس‌ها افزوده شود. این کار می‌تواند به صورت اتوماتیک انجام گیرد. همه‌ی ریپازیتوری‌هایی که از Entity Framework استفاده می‌کنند، باید از یک طول عمر مشابه استفاده کنند.

هشدار
خطر بزرگی را که باید در نظر گرفت، resolve کردن سرویس Scoped از طول عمر singleton می‌باشد. در صورت انجام این کار، احتمال دارد که سرویس‌ها وارد حالت نادرستی شوند.

سرویس‌هایی که وابستگی‌های دیگری هم دارند، باید آنها را در کانتینر ثبت کنند. اگر سازنده‌ی سرویس نیاز به یک primitive به عنوان ورودی داشته باشد، می‌توان با استفاده از الگوی گزینه‌ها و پیکربندی (options pattern and configuration)، ورودی‌های مناسبی را به سازنده‌ها منتقل کرد.


طول عمر سرویس‌ها و گزینه‌های ثبت

سرویس‌های ASP.NET را می‌توان با طول عمرهای زیر پیکربندی کرد:
Transient: سرویس‌هایی با طول عمر Transient، در هر زمان که درخواست می‌شوند، مجددا ایجاد می‌شوند. این طول عمر برای سرویس‌های سبک و بدون حالت مناسب می‌باشند.
Scoped: سرویس‌هایی با طول عمر Scoped، تنها یکبار در طی هر درخواست ایجاد می‌شوند.
Singleton: سرویس‌هایی با طول عمر Singleton، برای اولین باری که درخواست می‌شوند (یا اگر در ConfigureServices نمونه‌ای را مشخص کرده باشید) ایجاد می‌شوند و درخواست‌های آتی برای این سرویس‌ها از همان نمونه‌ی ایجاد شده استفاده می‌کنند. اگر اپلیکیشن شما درخواست رفتار singleton را داشته باشد، پیشنهاد می‌شود که سرویس کانتینر را برای مدیریت طول عمر سرویس مورد نیاز پیکربندی کنید و خودتان الگوی طراحی singleton را پیاده سازی نکنید.

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

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

using System;

namespace DependencyInjectionSample.Interfaces
{
    public interface IOperation
    {
        Guid OperationId { get; }
    }

    public interface IOperationTransient : IOperation
    {
    }
    public interface IOperationScoped : IOperation
    {
    }
    public interface IOperationSingleton : IOperation
    {
    }
    public interface IOperationSingletonInstance : IOperation
    {
    }
}
ما این اینترفیس‌ها را با استفاده از یک کلاس واحد به نام Operation پیاده سازی کرده‌ایم. سازنده‌ی این کلاس، یک Guid به عنوان ورودی می‌گیرد؛ یا اگر Guid برایش تامین نشد، خودش یک Guid جدید را می‌سازد.
سپس در ConfigureServices، هر نوع با توجه به طول عمر مورد نظر، به کانتینر افزوده می‌شود:
services.AddScoped<ICharacterRepository, CharacterRepository>();
services.AddTransient<IOperationTransient, Operation>();
services.AddScoped<IOperationScoped, Operation>();
services.AddSingleton<IOperationSingleton, Operation>();
services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty));
services.AddTransient<OperationService, OperationService>();
توجه داشته باشید که سرویس IOperationSingletonInstance، از یک نمونه‌ی خاص، با شناسه‌ی شناخته شده‌ی Guid.Empty استفاده می‌کند (این Guid فقط شامل اعداد صفر می‌باشد). بنابراین زمانیکه این تایپ مورد استفاده قرار می‌گیرد، کاملا واضح است. تمام این سرویس‌ها وابستگی‌های خود را به صورت پراپرتی نمایش می‌دهند. بنابراین می‌توان آنها را در View نمایش داد.

using DependencyInjectionSample.Interfaces;

namespace DependencyInjectionSample.Services
{
    public class OperationService
    {
        public IOperationTransient TransientOperation { get; }
        public IOperationScoped ScopedOperation { get; }
        public IOperationSingleton SingletonOperation { get; }
        public IOperationSingletonInstance SingletonInstanceOperation { get; }

        public OperationService(IOperationTransient transientOperation,
            IOperationScoped scopedOperation,
            IOperationSingleton singletonOperation,
            IOperationSingletonInstance instanceOperation)
        {
            TransientOperation = transientOperation;
            ScopedOperation = scopedOperation;
            SingletonOperation = singletonOperation;
            SingletonInstanceOperation = instanceOperation;
        }
    }
}
برای نشان دادن طول عمر اشیاء، در بین درخواست‌های جداگانه‌ی یک اپلیکیشن، مثال ذکر شده شامل کنترلر OperationsController می‌باشد که هر کدام از انواع IOperation و همچنین OperationService را درخواست می‌کند. سپس اکشن Index تمام مقادیر OperationId کنترل کننده و سرویس‌ها را نمایش می‌دهد:
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
using Microsoft.AspNetCore.Mvc;

namespace DependencyInjectionSample.Controllers
{
    public class OperationsController : Controller
    {
        private readonly OperationService _operationService;
        private readonly IOperationTransient _transientOperation;
        private readonly IOperationScoped _scopedOperation;
        private readonly IOperationSingleton _singletonOperation;
        private readonly IOperationSingletonInstance _singletonInstanceOperation;

        public OperationsController(OperationService operationService,
            IOperationTransient transientOperation,
            IOperationScoped scopedOperation,
            IOperationSingleton singletonOperation,
            IOperationSingletonInstance singletonInstanceOperation)
        {
            _operationService = operationService;
            _transientOperation = transientOperation;
            _scopedOperation = scopedOperation;
            _singletonOperation = singletonOperation;
            _singletonInstanceOperation = singletonInstanceOperation;
        }

        public IActionResult Index()
        {
            // viewbag contains controller-requested services
            ViewBag.Transient = _transientOperation;
            ViewBag.Scoped = _scopedOperation;
            ViewBag.Singleton = _singletonOperation;
            ViewBag.SingletonInstance = _singletonInstanceOperation;

            // operation service has its own requested services
            ViewBag.Service = _operationService;
            return View();
        }
    }
}

حالا دو درخواست جداگانه برای این کنترلر ساخته شده است:



به تفاوت‌های موجود در مقادیر OperationId در یک درخواست و بین درخواستها توجه کنید:
-  OperationId اشیاء Transient همیشه متفاوت می‌باشند. چون یک نمونه جدید برای هر کنترلر و هر سرویس ایجاد شده‌است.
- اشیاء Scoped در یک درخواست، یکسان هستند؛ اما در درخواست‌های مختلف متفاوت می‌باشند.
- اشیاء Singleton برای هر شی‌ء و هر درخواست (صرف نظر از اینکه یک نمونه در ConfigureServices ارائه شده است) یکسان می‌باشند.


درخواست سرویس

در ASP.NET سرویس‌های موجود در یک درخواست HttpContext از طریق مجموعه RequestServices قابل مشاهده می‌باشد.


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


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


طراحی سرویس‌ها برای تزریق وابستگی‌ها

شما باید سرویس‌های خود را طوری طراحی کنید که از تزریق وابستگی‌ها برای ارتباطات خود استفاده نمایند. این کار باعث کاهش استفاده از فراخوانی‌های متدهای استاتیک (متدهای استاتیک، حالت دار می‌باشند و استفاده‌ی زیاد از آنها باعث به وجود آمدن بوی بد کدی به نام static cling، می‌شود) و همچنین از بین رفتن نیاز به نمونه سازی مستقیم کلاس‌های وابسته داخل سرویس‌ها، می‌شود. هر موقع بخواهید بین new کردن یک کلاس، یا درخواست دادن آن از طریق تزریق وابستگی، یکی را انتخاب کنید، این اصطلاح را به یاد بیاورید،  New is Glue. با پیروی از اصول SOLID طراحی شیء گرا، به طور طبیعی کلاس‌های شما تمایل به کوچک بودن، کارا و قابل تست بودن را دارند.
اگر متوجه شدید که کلاس‌های شما تمایل دارند تا تعداد وابستگی‌های زیادی به آنها تزریق شود، چه باید بکنید؟ به طور کلی این مشکل نشانه‌ای است از نقض  Single Responsibility Principle یا SRP است و احتمالا کلاس‌های شما وظایف بیش از اندازه‌ای را دارند. در این گونه موارد تلاش کنید مقداری از وظایف کلاس را به یک کلاس جدید منتقل کنید. در نظر داشته باشید که کلاس‌های کنترلر باید به مسائل UI تمرکز کنند و قوانین کسب و کار و جزئیات دسترسی به داده‌ها باید در کلاس‌هایی جداگانه و مرتبط با خود قرار داشته باشند.
به طور خاص برای دسترسی به داده ، شما می‌توانید DbContext را به کنترلر‌های خود تزریق کنید (با فرض اینکه شما EF را به کانتینر سرویس ConfigureServices اضافه کرده‌اید). بعضی از توسعه دهندگان به جای تزریق مستقیم DbContext از یک اینترفیس ریپازیتوری استفاده می‌نمایند. می‌توانید با استفاده از یک اینترفیس برای کپسوله کردن منطق دسترسی به داده‌ها در یک مکان، تعداد تغییرات مورد نیاز را در صورت تغییر دیتابیس، به حداقل برسانید.


تخریب سرویس ها

سرویس کانتینر برای نوع‌های IDisposable که خودش ایجاد کرده‌است، متد Dispose را فراخوانی خواهد کرد. با این حال، اگر شما خودتان نمونه‌ای را به صورت دستی نمونه سازی و به کانتینر اضافه کرده باشید، سرویس کانتینر آنرا dispose نخواهد کرد.

مثال:
// Services implement IDisposable:
public class Service1 : IDisposable {}
public class Service2 : IDisposable {}
public class Service3 : IDisposable {}

public void ConfigureServices(IServiceCollection services)
{
    // container will create the instance(s) of these types and will dispose them
    services.AddScoped<Service1>();
    services.AddSingleton<Service2>();

    // container did not create instance so it will NOT dispose it
    services.AddSingleton<Service3>(new Service3());
    services.AddSingleton(new Service3());
}

نکته:
در نسخه 1.0، کانتینر برای تمام اشیاء از نوع IDisposable از جمله اشیائی که خودش ایجاد نکرده بود، متد dispose را فراخوانی می‌کرد.


سرویس‌های کانتینر جانشین

کانتینر موجود در net core. به منظور تامین نیازهای اساسی فریم ورک ایجاد شده‌است و تعداد زیادی از اپلیکیشن‌ها از آن استفاده می‌کنند. با این حال، توسعه دهندگان می‌توانند کانتینرهای مورد نظر خود را جایگزین آن کنند. متد ConfigureServices به طور معمول مقدار void را بر می‌گرداند. اما با تغییر امضای آن به نوع بازگشتیIServiceProvider، می‌توان سرویس کانتینر متفاوتی را در اپلیکیشن پیکربندی کرد. سرویس‌های کانتینر IOC مختلفی برای NET. وجود دارند؛ در مثال زیر، Autofac استفاده شده است.
در ابتدا بسته‌های زیر را نصب کنید:
Autofac
Autofac.Extensions.DependencyInjection
سپس کانتینر را در ConfigureServices پیکربندی کنید و  IServiceProvider را به عنوان خروجی بازگردانید:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    // Add other framework services

    // Add Autofac
    var containerBuilder = new ContainerBuilder();
    containerBuilder.RegisterModule<DefaultModule>();
    containerBuilder.Populate(services);
    var container = containerBuilder.Build();
    return new AutofacServiceProvider(container);
}


توصیه ها

هنگام کار با تزریق وابستگی‌ها، توصیه‌های ذیر را در نظر داشته باشید:
- DI برای اشیایی که دارای وابستگی پیچیده هستند، مناسب می‌باشد. کنترلرها، سرویس‌ها، آداپتورها و ریپازیتوری‌ها، نمونه‌هایی از این اشیاء هستند که می‌توانند به DI اضافه شوند.
- از ذخیره‌ی داده‌ها و پیکربندی مستقیم در DI اجتناب کنید. به عنوان مثال، معمولا سبد خرید کاربر نباید به سرویس کانتینر اضافه شود. پیکربندی باید از مدل گزینه‌ها استفاده کند. همچنین از اشیاء "data holder"، که فقط برای دسترسی دادن به اشیاء دیگر ایجاد شده‌اند، نیز اجتناب کنید. در صورت امکان بهتر است شیء واقعی مورد نیاز DI درخواست شود.
- از دسترسی استاتیک به سرویس‌ها اجتناب شود.
- از نمونه سازی مستقیم سرویس‌ها در کد برنامه خود اجتناب کنید.
- از دسترسی استاتیک به HttpContext اجتناب کنید.

توجه
مانند هر توصیه‌ی دیگری، ممکن است شما با شرایطی مواجه شوید که مجبور به نقض هر یک از این توصیه‌ها شوید. اما این موارد استثناء بسیار نادر می‌باشند و رعایت این نکات یک عادت برنامه نویسی خوب محسوب می‌شود.

مرجع: Introduction to Dependency Injection in ASP.NET Core
مطالب
مدیریت پیشرفته‌ی حالت در React با Redux و Mobx - قسمت هفتم - بررسی مفاهیم Mobx
MobX از 4 مفهوم اصلی تشکیل می‌شود:

- Observable state: در MobX نیز همانند Redux، کل شیء state به صورت یک شیء جاوا اسکریپتی ارائه می‌شود؛ با این تفاوت که در اینجا این شیء، یک Observable است که نمونه‌ای از مفهوم آن‌را در مثال قسمت قبل بررسی کردیم.
- Actions: متدهایی هستند که state را تغییر می‌دهند.
- Computed properties: در مورد مفهوم خواص محاسباتی در قسمت قبل بحث کردیم. این خواص، مقدار خود را بر اساس تغییرات سایر خواص Observable، به روز می‌کنند.
- Reactions: سبب بروز اثرات جانبی (side effects) می‌شوند؛ مانند تعامل با دنیای خارج. نمونه‌ای از آن، متد autorun است که تغییرات Observableها را ردیابی می‌کند.

برای مثال خاصیت محاسباتی fullName، تغییرات سایر خواص Observable را احساس کرده و مقدار خودش را به روز می‌کند. سپس یک Reaction به آن، می‌تواند به روز رسانی DOM، جهت نمایش این تغییرات باشد و یا نمونه‌ی دیگری که می‌تواند بسیاری از این مفاهیم را نمایش دهد، کلاس زیر است:
import { action, observable, computed } from 'mobx';

class PizzaCalculator {
  @observable numberOfPeople = 0;
  @observable slicesPerPerson = 2;
  @observable slicesPerPie = 8;

  @computed get slicesNeeded() {
    console.log('Getting slices needed');
    return this.numberOfPeople * this.slicesPerPerson;
   }

  @computed get piesNeeded() {
    console.log('Getting pies needed');
    return Math.ceil(this.slicesNeeded / this.slicesPerPie);
   }

   @action addGuest() {
     this.numberOfPeople!++;
   }
}
- دراینجا با استفاده از decorator syntax کتابخانه‌ی mobx، خواص و متدهای این کلاس معمولی ES6 را مزین کرده‌ایم.
- برای مثال زمانیکه تعریف observable numberOfPeople@ را داریم، به این معنا است که می‌خواهیم تغییرات تعداد افراد را تحت نظر قرار دهیم و اگر تغییری در مقدار آن صورت گرفت، آنگاه مقدار خواص محاسباتی که با computed@ مزین شده‌اند، به صورت خودکار به روز رسانی شوند.
- action@ به این معنا است که متدی در اینجا، سبب بروز تغییری در state کلاس جاری می‌شود. MobX به همراه یک strict mode است که اگر فعال باشد، ذکر تزئین کننده‌ی action@ بر روی یک چنین متدهایی ضروری است، در غیراینصورت، الزامی به درج آن نیست.

در این قطعه کد تعدای console.log را هم ملاحظه می‌کنید. علت آن نمایش مفهوم کش کردن اطلاعات در MobX است. فرض کنید برای بار اول، مقدار یکی از خواصی را که به صورت observable تعریف شده‌اند، تغییر می‌دهیم. در این حالت تمام خواص محاسباتی وابسته‌ی به آن‌ها، به صورت خودکار مجددا محاسبه شده و console.log‌ها را نیز مشاهده خواهیم کرد. اگر برای بار دوم همین فراخوانی صورت گیرد و تغییری در مقادیر خواص observable صورت نگیرد، MobX از نگارش کش شده‌ی این خواص محاسباتی استفاده می‌کند و بی‌جهت سبب رندر مجدد UI نخواهد شد که در نهایت کارآیی بالایی را سبب خواهد شد. برای پیاده سازی یک چنین قابلیتی با Redux باید از مفهومی مانند React.memo و Memoization و کتابخانه‌های کمکی مانند Reselect استفاده کرد؛ اما در اینجا به صورت توکار و خودکار اعمال می‌شود.


ساختارهای داده‌ای که توسط MobX پشتیبانی می‌شوند

MobX از اکثر ساختارهای داده‌ای متداول در جاوا اسکریپت پشتیبانی می‌کند؛ برای مثال:
- اشیاء مانند ({})observable
- آرایه‌ها مانند ([])observable
- Maps مانند observable(new Map())

چند نکته:
- همانطور که در قسمت قبل نیز ذکر شد، decorators در اصل یکسری تابع هستند و برای مثال می‌توان observable را به صورت observable@ و یا به صورت یک تابع معمولی مورد استفاده قرار داد.
- اگر شیء‌ای را به صورت ({})observable معرفی کنیم، با افزودن خواصی به آن پس از این فراخوانی، این خواص دیگر مورد ردیابی قرار نخواهند گرفت. علت آن‌را هم در شبه‌کد زیر می‌توان مشاهده کرد:
const extendObservable = (target, source) => {
  source.keys().forEach(key => {
    const wrappedInObservable = observable(source[key]);
    Object.defineProperty(target, key, {
      set: value.set.
      get: value.get
    });
  });
};
کاری که متد observable انجام می‌دهد، شمارش کلیدهای (خواص) شیء ارسالی به آن است و سپس محصور کردن آن‌ها درون یک شیء observable و در آخر بازگشت آن.
برای رفع این مشکل می‌توان از Map استفاده کرد. یعنی در اینجا اگر قرار است تعداد خواص اشیاء را به صورت پویا تغییر دهید، آن‌ها را به صورت Map تعریف کنید؛ چون متد set آن توسط observableها ردیابی می‌شود.


استفاده از MobX با React توسط کتابخانه‌ی mobx-react

تا اینجا MobX را به صورت متکی به خود مورد بررسی قرار دادیم. اکنون قصد داریم آن‌را به یک برنامه‌ی React متصل کنیم. برای اینکار کتابخانه‌های زیادی وجود دارند که در این قسمت کلیات روش کار با کتابخانه‌ی mobx-react را در بین آن‌ها بررسی می‌کنیم.

نصب کتابخانه‌ی mobx-react

ابتدا نیاز است تا این کتابخانه را نصب کنیم:
 > npm install --save mobx mobx-react

تحت نظر قرار دادن کامپوننت‌ها

در ادامه پس از نصب کتابخانه‌ی mobx-react، نیاز است کامپوننت‌ها را تحت نظر MobX قرار دهیم که اینکار را می‌توان توسط تزئین کننده‌ی observer آن انجام داد. همانطور که عنوان شد، تزئین کننده‌ها را می‌توان به صورت معمولی observer@ به یک کلاس و یا به صورت فراخوانی تابع، برای مثال به یک کامپوننت تابعی اعمال کرد. برای نمونه کامپوننت‌های کلاسی را به نحو زیر می‌توان با observer@ مزین کرد:
import { observer } from "mobx-react";

@observer class Counter extends Component {
در این حالت هر زمانیکه یکی از اشیاء observable تغییر می‌کند، React را وادار به رندر مجدد UI خواهد کرد.

و یا کامپوننت‌های تابعی را می‌توان توسط متد observer به صورت زیر محصور کرد:
const Counter = observer(({ count }) => {
  return (
   // ...
  );
});
با تحت نظر قرار گرفته شدن یک کامپوننت (چه با تزئین کننده‌ی observer@ و یا با بکارگیری نگارش تابعی آن)، منطقی که در پشت صحنه مورد استفاده قرار می‌گیرد، یک چنین شکلی را خواهد داشت (و برای اینکار نیازی به کد نویسی نیست):
class ContainerComponent extends Component () {
   componentDidMount() {
     this.stopListening = autorun(() => this.render());
   }

   componentWillUnmount() {
     this.stopListening();
   }

   render() { … }
}
زمانیکه کار رندر اولیه‌ی کامپوننت در DOM به پایان رسید، متد autorun به تغییرات observableها در پشت صحنه گوش‌فرا داده و سبب فراخوانی متد رندر کامپوننت، با هر تغییر لازمی می‌شود. این کاری است که متد یا تزئین کننده‌ی observer کتابخانه‌ی mobx-react انجام می‌دهد.

تعریف مخزن و اتصال آن به کامپوننت‌ها

کار شیء Provider که بالاترین کامپوننت را در سلسله مراتب کامپوننت‌ها محصور می‌کند، ارائه‌ی store، به تمام کامپوننت‌های فرزند است. در Redux فقط یک store را داریم که  به شیء Provider آن ارسال می‌کنیم. اما در حین کار با MobX چنین محدودیتی وجود ندارد و می‌توان چندین store را تعریف کرد و در اختیار برنامه قرار داد که شبه‌کد نحوه‌ی تعریف آن به صورت زیر است:
import { Provider } from 'mobx-react';

import ItemStore from './store/ItemStore';
import Application from './components/Application';

const itemStore = new ItemStore();

ReactDOM.render(
   <Provider itemStore={itemStore}>
     <Application />
   </Provider>,
   document.getElementById('root'),
);
در حین کار با Redux، قسمتی از مراحل تعریف آن، کار اتصال خواص موجود در state مخزن redux، به props یک کامپوننت است و یا همچنین کار اتصال رویدادها به props. یک چنین کاری را در اینجا به سادگی با تزئین کننده‌ای به نام inject می‌توان انجام داد که مخزن مورد استفاده را مشخص می‌کند:
@inject('itemStore')
class NewItem extends Component {
// ...
و یا برای کامپوننت‌های تابعی می‌توان از نگارش تابعی inject استفاده کرد. در این حالت، store تزریقی را می‌توان به صورت props دریافت نمود:
const UnpackedItems = inject('itemStore')(
    observer(({ itemStore }) => (
    // ...
  )),
);


یک مثال: پیاده سازی مثال شمارشگر قسمت سوم این سری با mobx-react

در ادامه قصد داریم برنامه‌ی شمارشگر ارائه شده در قسمت سوم بررسی redux را با mobx پیاده سازی کنیم. به همین جهت یک پروژه‌ی جدید React را ایجاد می‌کنیم:
> create-react-app state-management-with-mobx-part2
> cd state-management-with-mobx-part2
> npm start
در ادامه کتابخانه‌ها‌ی mobx ، mobx-react و همچنین بوت استرپ را نصب می‌کنیم. برای این منظور پس از باز کردن پوشه‌ی اصلی برنامه توسط VSCode، دکمه‌های ctrl+` را فشرده (ctrl+back-tick) و دستور زیر را در ترمینال ظاهر شده وارد کنید:
> npm install --save mobx mobx-react bootstrap
سپس برای افزودن فایل bootstrap.css به پروژه‌ی React خود، ابتدای فایل index.js را به نحو زیر ویرایش خواهیم کرد:
import "bootstrap/dist/css/bootstrap.css";

پس از آن فایل src\index.js را به صورت زیر تغییر می‌دهیم:
import "./index.css";
import "bootstrap/dist/css/bootstrap.css";

import { autorun, decorate, observable } from "mobx";
import React from "react";
import ReactDOM from "react-dom";

import Counter from "./components/Counter";
import * as serviceWorker from "./serviceWorker";

class Count {
  value = 0;

  increment = () => {
    this.value++;
  };

  decrement = () => {
    this.value--;
  };
}

decorate(Count, { value: observable });

const count = (window.count = new Count());
autorun(() => console.log("The count changed!", count.value));

ReactDOM.render(
  <main className="container">
    <Counter count={count} />
  </main>,
  document.getElementById("root")
);

serviceWorker.unregister();
توضیحات:
- در قسمت قبل، روش تحت نظر قرار دادن یک شیء متداول جاوا اسکریپتی را توسط متد observable مشاهده کردیم. در اینجا نگارش کلاسی آن مثال را بر اساس کلاس Count مشاهده می‌کنید. اگر نخواهیم از decorator ای مانند observable@ بر روی خاصیت value این کلاس استفاده کنیم، روش تابعی آن‌را با فراخوانی متد decorate و ذکر نوع کلاس و سپس خاصیتی که باید به صورت observable تحت نظر قرار گیرد، در اینجا مشاهده می‌کنید. این هم یک روش کار با mobx است.
- پس از ایجاد کلاس Count که در اینجا نقش store را نیز بازی می‌کند، یک وهله‌ی جدید را از آن ساخته و به متغیر count در این ماژول و همچنین window.count انتساب می‌دهیم. انتساب window.count سبب می‌شود تا بتوان در کنسول توسعه دهندگان مرورگر، با نوشتن count و سپس enter، به محتویات این متغیر دسترسی یافت و یا حتی آن‌را تغییر داد؛ مانند تصویر زیر که بلافاصله این تغییر، در UI برنامه نیز منعکس می‌شود:


- در اینجا تعریف شیء Provider را که پیشتر در مورد آن بحث کردیم، مشاهده نمی‌کنید؛ چون با تک کامپوننت Counter تعریف شده‌ی در این مثال، نیازی به آن نیست. می‌توان این شیء store را به صورت مستقیم به props کامپوننت Counter ارسال کرد.

اکنون تعریف کامپوننت شمارشگر واقع در فایل src\components\Counter.jsx به صورت زیر خواهد بود که این کامپوننت، count را به صورت props دریافت می‌کند:
import { observer } from "mobx-react";
import React from "react";

const Counter = observer(({ count }) => {
  return (
    <section className="card mt-5">
      <div className="card-body text-center">
        <span className="badge m-2 badge-primary">{count.value}</span>
      </div>
      <div className="card-footer">
        <div className="d-flex justify-content-center align-items-center">
          <button
            className="btn btn-secondary btn-sm"
            onClick={count.increment}
          >
            +
          </button>
          <button
            className="btn btn-secondary btn-sm m-2"
            onClick={count.decrement}
          >
            -
          </button>
        </div>
      </div>
    </section>
  );
});

export default Counter;
و سپس بر اساس count رسیده، در اینجا می‌توان مستقیما متدهای کلاس Count را فراخوانی کرد (مانند count.increment؛ که البته در اصل یک خاصیت است که با متدی مقدار دهی شده‌است) و یا مقدار خاصیتی از آن‌را مانند count.value، نمایش داد.
تا زمانیکه کامپوننت، با تابع observer محصور شده‌است، به props رسیده گوش فرا داده و خواص و اشیاء observable آن‌را تشخیص می‌دهد و سبب رندر مجدد کامپوننت، با تغییری در آن‌ها خواهد شد.

کدهای کامل این قسمت را می‌توانید از اینجا دریافت کنید: state-management-with-mobx-part2.zip
مطالب
Functional Programming یا برنامه نویسی تابعی - قسمت دوم – مثال‌ها
در قسمت قبلی این مقاله، با مفاهیم تئوری برنامه نویسی تابعی آشنا شدیم. در این مطلب قصد دارم بیشتر وارد کد نویسی شویم و الگوها و ایده‌های پیاده سازی برنامه نویسی تابعی را در #C مورد بررسی قرار دهیم.


Immutable Types

هنگام ایجاد یک Type جدید باید سعی کنیم دیتای داخلی Type را تا حد ممکن Immutable کنیم. حتی اگر نیاز داریم یک شیء را برگردانیم، بهتر است که یک instance جدید را برگردانیم، نه اینکه همان شیء موجود را تغییر دهیم. نتیحه این کار نهایتا به شفافیت بیشتر و Thread-Safe بودن منجر خواهد شد.
مثال:
public class Rectangle
{
    public int Length { get; set; }
    public int Height { get; set; }

    public void Grow(int length, int height)
    {
        Length += length;
        Height += height;
    }
}

Rectangle r = new Rectangle();
r.Length = 5;
r.Height = 10;
r.Grow(10, 10);// r.Length is 15, r.Height is 20, same instance of r
در این مثال، Property های کلاس، از بیرون قابل Set شدن می‌باشند و کسی که این کلاس را فراخوانی میکند، هیچ ایده‌ای را درباره‌ی مقادیر قابل قبول آن‌ها ندارد. بعد از تغییر بهتر است وظیفه‌ی ایجاد آبجکت خروجی به عهده تابع باشد، تا از شرایط ناخواسته جلوگیری شود:
// After
public class ImmutableRectangle
{
    int Length { get; }
    int Height { get; }

    public ImmutableRectangle(int length, int height)
    {
        Length = length;
        Height = height;
    }

    public ImmutableRectangle Grow(int length, int height) =>
          new ImmutableRectangle(Length + length, Height + height);
}

ImmutableRectangle r = new ImmutableRectangle(5, 10);
r = r.Grow(10, 10);// r.Length is 15, r.Height is 20, is a new instance of r
با این تغییر در ساختار کد، کسی که یک شیء از کلاس ImmutableRectangle را ایجاد میکند، باید مقادیر را وارد کند و مقادیر Property ها به صورت فقط خواندنی از بیرون کلاس در دسترس هستند. همچنین در متد Grow، یک شیء جدید از کلاس برگردانده می‌شود که هیچ ارتباطی با کلاس فعلی ندارد.


استفاده از Expression بجای Statement

یکی از موارد با اهمیت در سبک کد نویسی تابعی را در مثال زیر ببینید:
public static void Main()
{
    Console.WriteLine(GetSalutation(DateTime.Now.Hour));
}

// imparitive, mutates state to produce a result
/*public static string GetSalutation(int hour)
{
    string salutation; // placeholder value

    if (hour < 12)
        salutation = "Good Morning";
    else
        salutation = "Good Afternoon";

    return salutation; // return mutated variable
}*/

public static string GetSalutation(int hour) => hour < 12 ? "Good Morning" : "Good Afternoon";
به خط‌های کامنت شده دقت کنید؛ می‌بینیم که یک متغیر، تعریف شده که نگه دارنده‌ای برای خروجی خواهد بود. در واقع به اصطلاح آن را mutate می‌کند؛ در صورتیکه نیازی به آن نیست. ما می‌توانیم این کد را به صورت یک عبارت (Expression) در آوریم که خوانایی بیشتری دارد و کوتاه‌تر است.


استفاده از High-Order Function ها برای ایجاد کارایی بیشتر

در قسمت قبلی درباره توابع HOF صحبت کردیم. به طور خلاصه توابعی که یک تابع را به عنوان ورودی میگیرند و یک تابع را به عنوان خروجی برمی‌گردانند. به مثال زیر توجه کنید:
public static int Count<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    int count = 0;

    foreach (TSource element in source)
    {
        checked
        {
            if (predicate(element))
            {
                count++;
            }
        }
    }

    return count;
}
این قطعه کد، مربوط به متد Count کتابخانه‌ی Linq می‌باشد. در واقع این متد تعدادی از چیز‌ها را تحت شرایط خاصی می‌شمارد. ما دو راهکار داریم، برای هر شرایط خاص، پیاده سازی نحوه‌ی شمردن را انجام دهیم و یا یک تابع بنویسیم که شرط شمردن را به عنوان ورودی دریافت کند و تعدادی را برگرداند.


ترکیب توابع

ترکیب توابع به عمل پیوند دادن چند تابع ساده، برای ایجاد توابعی پیچیده گفته می‌شود. دقیقا مانند عملی که در ریاضیات انجام می‌شود. خروجی هر تابع به عنوان ورودی تابع بعدی مورد استفاده قرار میگیرد و در آخر ما خروجی آخرین فراخوانی را به عنوان نتیجه دریافت میکنیم. ما میتوانیم در #C به روش برنامه نویسی تابعی، توابع را با یکدیگر ترکیب کنیم. به مثال زیر توجه کنید:
public static class Extensions
{
    public static Func<T, TReturn2> Compose<T, TReturn1, TReturn2>(this Func<TReturn1, TReturn2> func1, Func<T, TReturn1> func2)
    {
        return x => func1(func2(x));
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        Func<int, int> square = (x) => x * x;
        Func<int, int> negate = x => x * -1;
        Func<int, string> toString = s => s.ToString();
        Func<int, string> squareNegateThenToString = toString.Compose(negate).Compose(square);
        Console.WriteLine(squareNegateThenToString(2));
    }
}
در مثال بالا ما سه تابع جدا داریم که میخواهیم نتیجه‌ی آن‌ها را به صورت پشت سر هم داشته باشیم. ما میتوانستیم هر کدام از این توابع را به صورت تو در تو بنویسیم؛ ولی خوانایی آن به شدت کاهش خواهد یافت. بنابراین ما از یک Extension Method استفاده کردیم.


Chaining / Pipe-Lining و اکستنشن‌ها

یکی از روش‌های مهم در سبک برنامه نویسی تابعی، فراخوانی متد‌ها به صورت زنجیره‌ای و پاس دادن خروجی یک متد به متد بعدی، به عنوان ورودی است. به عنوان مثال کلاس String Builder یک مثال خوب از این نوع پیاده سازی است. کلاس StringBuilder از پترن Fluent Builder استفاده می‌کند. ما می‌توانیم با اکستنشن متد هم به همین نتیجه برسیم. نکته مهم در مورد کلاس StringBuilder این است که این کلاس، شیء string را mutate نمیکند؛ به این معنا که هر متد، تغییری در object ورودی نمی‌دهد و یک خروجی جدید را بر می‌گرداند.
string str = new StringBuilder()
  .Append("Hello ")
  .Append("World ")
  .ToString()
  .TrimEnd()
  .ToUpper();
در این مثال  ما کلاس StringBuilder را توسط یک اکستنشن متد توسعه داده‌ایم:
public static class Extensions
{
    public static StringBuilder AppendWhen(this StringBuilder sb, string value, bool predicate) => predicate ? sb.Append(value) : sb;
}

public class Program
{
    public static void Main(string[] args)
    {
        // Extends the StringBuilder class to accept a predicate
        string htmlButton = new StringBuilder().Append("<button").AppendWhen(" disabled", false).Append(">Click me</button>").ToString();
    }
}


نوع‌های اضافی درست نکنید ، به جای آن از کلمه‌ی کلیدی yield استفاده کنید!

گاهی ما نیاز داریم لیستی از آیتم‌ها را به عنوان خروجی یک متد برگردانیم. اولین انتخاب معمولا ایجاد یک شیء از جنس List یا به طور کلی‌تر Collection و سپس استفاده از آن به عنوان نوع خروجی است:
public static void Main()
{
    int[] a = { 1, 2, 3, 4, 5 };

    foreach (int n in GreaterThan(a, 3))
    {
        Console.WriteLine(n);
    }
}


/*public static IEnumerable<int> GreaterThan(int[] arr, int gt)
{
    List<int> temp = new List<int>();
    foreach (int n in arr)
    {
        if (n > gt) temp.Add(n);
    }
    return temp;
}*/

public static IEnumerable<int> GreaterThan(int[] arr, int gt)
{
    foreach (int n in arr)
    {
        if (n > gt) yield return n;
    }
}
همانطور که مشاهده میکنید در مثال اول، ما از یک لیست موقت استفاده کرد‌ه‌ایم تا آیتم‌ها را نگه دارد. اما میتوانیم از این مورد با استفاده از کلمه کلیدی yield اجتناب کنیم. این الگوی iterate بر روی آبجکت‌ها در برنامه نویسی تابعی، خیلی به چشم میخورد.


برنامه نویسی declarative به جای imperative با استفاده از Linq

در قسمت قبلی به طور کلی درباره برنامه نویسی Imperative صحبت کردیم. در مثال زیر یک نمونه از تبدیل یک متد که با استایل Imperative نوشته شده به declarative را می‌بینید. شما میتوانید ببینید که چقدر کوتاه‌تر و خواناتر شده:
List<int> collection = new List<int> { 1, 2, 3, 4, 5 };

// Imparative style of programming is verbose
List<int> results = new List<int>();

foreach(var num in collection)
{
  if (num % 2 != 0) results.Add(num);
}

// Declarative is terse and beautiful
var results = collection.Where(num => num % 2 != 0);


Immutable Collection

در مورد اهمیت immutable قبلا صحبت کردیم؛ Immutable Collection ها، کالکشن‌هایی هستند که به جز زمانیکه ایجاد می‌شنود، اعضای آن‌ها نمی‌توانند تغییر کنند. زمانیکه یک آیتم به آن اضافه یا کم شود، یک لیست جدید، برگردانده خواهد شد. شما می‌توانید انواع این کالکشن‌ها را در این لینک ببینید.
به نظر میرسد که ایجاد یک کالکشن جدید میتواند سربار اضافی بر روی استفاده از حافظه داشته باشد، اما همیشه الزاما به این صورت نیست. به طور مثال اگر شما f(x)=y را داشته باشید، مقادیر x و y به احتمال زیاد یکسان هستند. در این صورت متغیر x و y، حافظه را به صورت مشترک استفاده می‌کنند. به این دلیل که هیچ کدام از آن‌ها Mutable نیستند. اگر به دنبال جزییات بیشتری هستید این مقاله به صورت خیلی جزیی‌تر در مورد نحوه پیاده سازی این نوع کالکشن‌ها صحبت میکند. اریک لپرت یک سری مقاله در مورد Immutable ها در #C دارد که میتوانید آن هار در اینجا پیدا کنید.

 

Thread-Safe Collections

اگر ما در حال نوشتن یک برنامه‌ی Concurrent / async باشیم، یکی از مشکلاتی که ممکن است گریبانگیر ما شود، race condition است. این حالت زمانی اتفاق می‌افتد که دو ترد به صورت همزمان تلاش میکنند از یک resource استفاده کنند و یا آن را تغییر دهند. برای حل این مشکل میتوانیم آبجکت‌هایی را که با آن‌ها سر و کار داریم، به صورت immutable تعریف کنیم. از دات نت فریمورک نسخه 4 به بعد  Concurrent Collection‌ها معرفی شدند. برخی از نوع‌های کاربردی آن‌ها را در لیست پایین می‌بینیم:
Collection
توضیحات
 ConcurrentDictionary 
  پیاده سازی thread safe از دیکشنری key-value 
 ConcurrentQueue 
  پیاده سازی thread safe از صف (اولین ورودی ، اولین خروجی) 
 ConcurrentStack 
  پیاده سازی thread safe از پشته (آخرین ورودی ، اولین خروجی) 
 ConcurrentBag 
  پیاده سازی thread safe از لیست نامرتب 

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

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