چنانچه همه ما مطلع هستيم کليد واژه yield در C# 2.0 به اين زبان اضافه شد و معمولا در دو شکل yield return… , yield break… مورد استفاده قرار ميگيرد.
استفاده از yield در يک متد، يا Accessor (يا حتی متد Overload کننده يک اپراتور، و در مجموع اشاره به محل های مجاز استفاده از yield) علاوه بر اينکه متد يا Accessor مورد نظر را بوضوح به يک Iterator تبديل ميکند ، موجب ميشود به واسطه تمهيداتی که ايجاد شده نياز به يک کلاس جداگانه برای حفظ ، ذخيره و بازيابی State در خلال پيمايش يک IEnumerable يا شکل جنريک آن يعنی IEnemerable<T> نيز مرتفع گردد . در مثال ساده زير يک شکل خاص از فراخوانی yield نشان داده شده است :
public void Consumer() { foreach(int i in SomeInegers()) { Console.WriteLine(i.ToString()); } }
public IEnumerable<int> SomeInegers() { yield return 1; yield return 2; yield return 4; yield return 8; yield return 16; yield return 16777216; }
در اين مثال ساده ملاحظه ميکنيد که چگونه متد SomeIntegers چنانچه از نوع خروجی آن نيز مشخص است می تواند (به شکل نسبتا غريب و غير معمولی) در يک حلقه foreach به عنوان Iterator مورد استفاده قرار بگيرد .
با يک بررسی ساده توسط ildasm بوضوح می توان مشاهده کرد که هر گاه يک Member Function با استفاده از کليدواژه yield اقدام پياده سازی يک iterator نمايد، CLR در مواجهه با آن به منظوردستيابی به اعضای مجموعه (آرايه، ليست و يا اصولا هر مفهومی که Iterate در آن دارای مفهوم باشد) کلاس مخصوصی با ويژگيهای خاص ايجاد می کند که بر اساس خواص و نوع عملکرد اين کلاس می توان صريحا اظهار نظر کرد که يک State Machine ايجاد شده است . بنا بر تعريف حداقل در دنيای OOP يک State Machine کلاس ويژه ايست که چنانچه از نام آن نيز مشخص می باشد دارای چندين State يا وضعيت مختلف می باشد که تغيير يا Transition از يک State به State ديگر با توجه به يک شاخص (معمولا يک Data Member) موجب تغيير در Behavour و رفتار کلاس مورد نظر می باشد و به همين دليل مفهوم State در State Machine در واقع اشاره به يک شرايط منحصر بفرد يا Unique Condition می باشد . تغيير در State که اصطلاحا Transition ناميده ميشود در مورد State Machine ايجاد شده توسط CLR (هنگام مواجهه با yield) و در واکنش به يک Event و در خلال فراخوانی های مکرر MoveNext انجام میگردد . اين موضوع تغييراتی در مسير پردازش خطی ايجاد می کند که موضوع اين بحث توضيح مختصری در اين باره می باشد و به طور مشخص در اينجا State Machine ايجاد شده توسط کامپايلر ، بررسی شده و وجود اين تغييرات در مسير پردازش با يک مثال ساده بيان شده است . اين مثال شکل دستکاری شده ای از مثال موجود در کتاب Expert C# 5.0 اثر مشهور Mohammad Rahman از انتشارات APress می باشد و ايده اصلی از فصل نهم همين کتاب ارزشمند الهام گرفته شده و اتفاقا اين بحث در کتاب مذکوربا تاکيد بر جزييات عملکرد State Machine و مراحل تغيير Transition تا حد مطلوبی موشکافی و توصيف شده است . حتی در MSDN نيز به منظور نمايش نحوه استفاده از yield از همين ايده محاسبه توان و متدی به نام Power که شباهت زيادی به متد مثال ما هم دارد استفاده شده است . اما عصاره مطلب ما حول و حوش اين مفهوم دور می زند که کامپايلر به محض برخورد با yield ، کنترل را به مبدا فراخواننده باز گردانده و يا واگذار می کند اما قبل از انجام اين کار State و وضعيت Callee يا همان “فراخوانده شده” را (به منظور امکان بازيابی State در صورت نياز به مراجعه مجدد ، دقيقا به وضعيتی که مسير پردازش به فراخوان منتقل شده) ذخيره می نمايد. اين وضعيت تنها در صورتی مجاز است که Iterator Block قادر باشد يکی از دو نوع Type زير را به فراخواننده عودت دهد:
1) Enumerator: System.Collections.IEnumerator يا System.Collections.Generic. IEnumerator<T>. 2) Enumerable: System.Collections.IEnumerable يا System.Collections.Generic. IEnumerable<T>.
برای بررسی دقيق تر اين State Machine و جزييات عملکرد آن متد Power در مثال مطرح شده است که محاسبه توان يک عدد را بر اساس مقدار داده شده محاسبه ميکند ولی به جای روشهای عادی در داخل يک حلقه از yield برای بازگرداندن نتيجه، تا زمانيکه شرط counter++ < exponent محقق باشد، استفاده ميکند. به منظور درک دقيق تری از اهداف مثال، بلافاصله بعد از yield يک دستور ارسال پايه و توان به خروجی قرار داده شده است . چنانچه قبلا توضيح داده شد کامپايلر در مواجهه با yield مبادرت به ايجاد يک State Machine با خصوصاتی که در ادامه مطرح شده است، خواهد نمود و چنانچه مشاهده خواهيم کرد بعد از انتقال مسير پردازش به فراخوان مجددا به محل قبلی بازگشته و پردازش ادامه خواهد يافت. ابتدا کد کامل مثال را جهت مراجعه بهتر در اينجا مشاهده ميکنيد:
using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;
namespace StateMachineToy { class Program { static void Main(string[] args) { List<int> intList = Enumerable.Range(2, 8).ToList(); intList.ForEach(delegate(int i) { Console.WriteLine(“\t{0}”, i); foreach (var num in Power(i, 9)) { Console.Write(“\t{0,12} = “, num); }
}); Console.ReadLine(); } public static IEnumerable Power(int number, int exponent) { int counter = 0; int result = 1; while (counter++ < exponent) { result = result * number; yield return result; Console.WriteLine(“{0}^{1}”,number, counter); } } } }
در آغاز مثال، برای به دست آوردن يک رنج از اعداد صحيح بين 1 تا 9 از Enumerable.Range استفاده شده است که اعضای اين ليست، به عنوان ارائه پايه به متد Power مورد استفاده قرار ميگيرند .اين مقادير در داخل يک ليست جنريک ساده ذخيره ميشود تا با استفاده از يک حلقه foreach يا به طور مشابه استفاده از متد ForEach (که انتخاب من نيز همين بوده است) امکان فراخوانی متد Power به ازای هر يک از آيتم های اين ليست فراهم باشد . چنانچه مطلع هستيد متد ForEach امکان ورود يک delegate استاندارد Action را به عنوان پارامتر، فراهم ميکند که امکان انجام عمليات مورد نظر را به ازای هر يک از اعضای ليست فراهم می نمايد . شکل کلی استفاده از delegate استاندارد Action در داخل متد ForEach، به صورت زير می باشد: public void ForEach( Action<T> action )
در ادامه از آنجاييکه فراخوانی yield متد Power ما را تلويجا به يک Enumerable مبدل می کند پس امکان استفاده از آن در داخل حلقه foreach فراهم می باشد که هر بار پايه مورد نظر ما را از ليست ilist (که چنانچه مشاهده کرديم مقادير آن به سادگی با فراخوانی متد Range توليد شده است) دريافت کرده و به عنوان پارامتر اول متد Power آن راوارد متد ميکند . پارامتر دوم به صورت ثابت عدد 9 در نظر گرفته شده است که مفهوم آن اين است که امکان محاسبه توانهای 1 تا 9 را فراهم کرده و با استفاده از ويژگی انتقال پردازش در State Machine ايجاد شده هر بار مقدار صحيح توان مورد نظر را محاسبه کرده و مسير پردازش مجددا به فراخوان منتقل شود که چنانچه در کد نيز بوضوح مشخص است يک دستور Console.Write هر بار مقدار محاسبه شده را در داخل بلاک فراخواننده (و نه در داخل خود متد) نمايش ميدهد . توجه به فرمت يک نمونه از اعداد موجود در خروجی (به طور مثال 5^2=32) بوضوح نشان ميدهد که درست در چه زمانی انتقال بين فراخوان و فراخواننده انجام ميشود. به طور مشخص علامت = و مقدار سمت چپ آن توسط بلاک اصلی برنامه و آنچه در سمت راست علامت = قرار دارد در داخل متد Power ايجاد ميگردد . خروج موقت از متد Power به دليل ويژگی State Machine منجر ميشود که هر بار State مورد نياز و ضروری در خلال اين انتقال وضعيت، حفظ گردد . با بررسی کد il توليد شده (با استفاده از يوتيليتی ildasm) ملاحظه خواهيم کرد که چنانچه انتظار آن نيز وجد دارد يک State Machine در غالب يک کلاس به نام <Power>d__0 توسط CLR ايجاد ميشود . اين State Machine با استفاده از همين تکنيک ساده، امکان Iterate يا پيمايش اعضای يک مجموعه يا ليست Enumerable را فراهم ميکند که به طور مشخص با تغيير State در داخل فراخوانی های مکرر Move Next تا زمانيکه MoveNext مقدار True را گزارش نمايد (به عبارت ساده تر يعنی هنوز عضو پيمايش نشده ای وجود دشته باشد) انجام خواهد شد. بررسی دقيق تر خروجی حاصل از ildasm نشان ميدهد که اين State Machine ايجاد شده، دارای چهار State يا وضعيت مختلف به شرح زير می باشد : 1) Before يا قبلی با مقدار 0 2) Running يا در حال اجرا با مقدار -1 3) Suspended يا وضعيت تعليق با مقدار 1 4) After يا بعدی با هر مقدار مثبت بزگتر از يک در داخل MoveNext و صرفا به منظور صدور اجازه امکان ادامه Iterationيا يپيمايش، CLR مقدار State را از وضعيت Before با مقدار صفر به وضعيت Running با مقدار -1 تنظيم می کند . اين دقيقا زمانيست که CLR مقدار متغير موجود در حلقه foreach را تنظيم می کند . (ياد آوری ميکنم که اين متغير می تواند Typed و يا با استفاده از var دايناميک باشد .نظير متغير num در مثال ما) . اين درست همان وضعيتی است که CLR کد موجود در Iterator Block را اجرا می کند. تغيير وضعيت از Running به Suspend نشانه ای برای اجبار به بازيابی State قبلی و همچنين ذخيره State جاری توسط State Machine می باشد. در اينجا بلا فاصله پردازش به بعد از دستور yield منتقل ميگردد که ما در مثال از اين ويژگی در جهت اهداف برنامه استفاده کرده ايم . تغيير به وضعيت After برای State Machine نشانه ای بر خروج False در فراخوانی MoveNext می باشد که واضح است که به صورت عدم وجود آيتمی برای ادامه حلقه تفسير خواهد شد .
به طور مشخص با مطالعه آنچه ildasm به ما نشان ميدهد متوجه ميشويم که تغييرات State (که مجددا تاکيد ميکنم بهتر است اين تغيير State را Transition بناميم) و به طور کلی قسمت اصلی کار State Machine در داخل متد MoveNextی که به عنوان جزيی از State Machine و توسط کامپايلر ايجاد شده، انجام ميشود . اينجا جايی است که بوضوح با يک پياده سازی بلاک switch() …. Case برای تغيير Transition در شرايط مناسب و مورد نياز روبرو خواهيم شد . نمونه ای از خروجی برنامه به منظور حفظ فرمت چپ به راست برنامه در بخش پایانی قابل مشاهده است . نویسنده : مهندس مهران حسین نیا – برگفته از گروه برنامه نویسی asp.net mvc در فیسبوک.
22 = 2^14 = 2^28 = 2^316 = 2^432 = 2^564 = 2^6128 = 2^7256 = 2^8512 = 2^933 = 3^19 = 3^227 = 3^381 = 3^4243 = 3^5729 = 3^62187 = 3^76561 = 3^819683 = 3^944 = 4^116 = 4^264 = 4^3256 = 4^41024 = 4^54096 = 4^616384 = 4^765536 = 4^8262144 = 4^955 = 5^125 = 5^2125 = 5^3625 = 5^43125 = 5^515625 = 5^678125 = 5^7390625 = 5^81953125 = 5^966 = 6^136 = 6^2216 = 6^31296 = 6^47776 = 6^546656 = 6^6279936 = 6^71679616 = 6^810077696 = 6^977 = 7^149 = 7^2343 = 7^32401 = 7^416807 = 7^5117649 = 7^6823543 = 7^75764801 = 7^840353607 = 7^988 = 8^164 = 8^2512 = 8^34096 = 8^432768 = 8^5262144 = 8^62097152 = 8^716777216 = 8^8134217728 = 8^999 = 9^181 = 9^2729 = 9^36561 = 9^459049 = 9^5531441 = 9^64782969 = 9^743046721 = 9^8387420489 = 9^9