اجرای با تاخیر (deferred execution)

قبل از پرداختن به درس‌های بعدی، به این نکته توجه کنید که LINQ برای انجام پرس و جوها از deferred execution یا اجرای با تأخیر استفاده می‌کند. به زبان ساده به این معنی است که عبارت پرس و جوی واقعی اجرا نمی‌شود مگر زمانی که از نتایج پرس و جو استفاده شود. مثال زیر را در نظر بگیرید :

  1: int[] numbers = { 1, 2, 3, 4, 5 };
  2: 
  3: int i = 3;
  4: 
  5: var result = from n in numbers
  6:                 where n <= i  
  7:                 select n;     
  8: 
  9: i = 4;
 10: 
 11: foreach (var n in result)
 12: {
 13:     Console.WriteLine(n);
 14: }
1
2
3
4

عملگر where را در درس‌های بعدی به طور مفصل توضیح می‌دهیم ولی برای این مثال باید توضیح مختصری از آن را ارائه دهیم. عملگر where بر اساس یک شرط نتایج حاصل از یک پرس و جو را فیلتر می‌کند. در خط 6 از مثال بالا عملگر where فقط مقادیری را بر می‌گرداند که کوچکتر یا مساوی با مقدار i یعنی 3 باشند. در درس‌های آینده به طور مفصل در رابطه با این عملگر توضیح خواهیم داد. اجازه دهید بر روی deferred execution تمرکز کنیم.

در خط 3 مثال بالا مقدار 3 به متغیر i نسبت داده شده است. بنابراین، وقتی عبارت پرس و جوی خطوط 7-5 اجرا می‌شود، عملگر where هر عضو منبع داده را با مقدار 3 مقایسه می‌کند، اگر عضو مورد نظر کوچکتر یا مساوی با این مقدار باشد در نتیجه پرس و جو قرار می‌گیرد. حالا ممکن است که فکر کنید که متغیر result شامل اعداد 1، 2 و 3 است. اما در حقیقت اینطور نیست. از آنجاییکه در خط 9 مقدار متغیر i به 4 تغییر کرده است و به دلیل اینکه Linq از اجرای با تأخیر در بسیاری از عملگرهای خود استفاده می‌کند، مقدار جدید متغیر به طور مستقیم بر روی نتیجه عبارت پرس و جو در خطوط 5 تا 7 تأثیر می‌گذارد.

همانطور که قبلاً گفته شد، این عبارت پرس وجو فقط یکبار در برنامه اجرا می‌شود. حلقه foreach در خطوط 11 تا 14 عبارت پرس و جو را اجرا می‌کند و اولین آیتم آنرا بر می‌گرداند. به این نکته توجه کنید چون ما مقدار متغیر i را تغییر دادیم، عبارت پرس و جو به روز رسانی می‌شود. حلقه foreach به جای نمایش مقادیر 1 تا 3 مقادیر 1 تا 4 را نمایش می‌دهد (به این خاطر که آخرین مقدار i برابر 4 است).

یکی از کاربردهای اجرای با تأخیر نیز همین است. برنامه به شما این اجازه را می‌دهد که قبل از اجرای عبارت پرس و جو و بدست آوردن مقادیر آنرا به روزرسانی کنید. اجرای با تأخیر رفتار پیشفرض Linq در اجرای عبارات پرس و جو است، اما شما می‌توانید آن را تغییر داده و کاری کنید که پرس و جو فوراً انجام شود. برای اینکار شما باید از مجموعه‌ای از متدها در فضای نامی System.Linq استفاده کنید که شامل ToArray<T> ،ToList<T> ،ToDictionary<T> ،ToLookUp<T> می‌شود.

این متدها جزء متدهای الحاقی رابط IEnumerable<T> هستند. به عنوان مثال، برای تبدیل نتیجه یک عبارت پرس و جو به کلکسیونی از نوع List<T> از متد ToList<T> استفاده می‌شود. مثال زیر نشان می‌دهد که چگونه با استفاده از متد ToList<T> بلادرنگ یک عبارت پرس و جو را اجرا کنیم:

  1: int[] numbers = { 1, 2, 3, 4, 5 };
  2:  
  3: int i = 3;
  4:  
  5: var result = (from n in numbers
  6:               where n <= i
  7:               select n).ToList();
  8:  
  9: i = 4;
 10:  
 11: foreach (var n in result)
 12: {
 13:     Console.WriteLine(n);
 14: }
1
2
3

از آنجاییکه عبارت پرس و جو در کد بالا فوراً اجرا می‌شود، وقتی که برنامه به خط 7 می‌رسد، نتایج حاصل از پرس و جو در متغیر result قرار می‌گیرند و حتی اگر شما در خط 9 مقدار متغیر i را تغییر دهید باز هم اعداد 1 تا 3 به جای اعداد 1 تا 4 در خروجی نمایش داده می‌شوند. به این نکته توجه کنید که برخی از متدهای توسعه یافته فضای نامی System.Linq باعث اجرای با تأخیر و برخی دیگر از آنها باعث اجرای فوری پرس و جو می‌شوند. مثلاً متد ()Select برای اجرای با تأخیر و متد ()ToList برای اجرای فوری عبارت پرس و جو به کار می‌رود. مثلاً در پرس و جوی زیر از روش متدی و متد ()Select برای به دست آوردن عناصر یک آرایه استفاده شده است:

var result = numbers.Select(n => n);

از آنجاییکه متد ()Select از اجرای با تأخیر استفاده می‌کند، کد فوق تا زمانی که حلقه foreach تمامی نتایج را پیمایش نکند اجرا نمی‌شود. در عوض اگر بعد از متد ()Select از متدی که باعث اجرای فوری پرس و جو می‌شود مانند متد ()ToList استفاده کنید، عبارت پرس و جو فوراً اجرا می‌شود:

var result = numbers.Select(n => n).ToList();

پس در نتیجه متد آخر تعیین کننده اجرا یا عدم اجرای با تأخیر عبارت پرس و جو می‌باشد. جدول زیر متدهای فضای نام System.Linq را نشان می‌دهد. این جدول همچنین نشان می‌دهد که فراخوانی کدام یک از متدها باعث اجرای با تأخیر عبارات پرس و جو می‌شوند :

متد اجرا متد اجرا
Distinct با تأخیر Reverse با تأخیر
DefaultIfEmpty با تأخیر Select با تأخیر
ElementAt فوری SelectMany با تأخیر
ElementAtOrDefault با تأخیر SequenceEqual فوری
Except با تأخیر Single فوری
First فوری SingleOrDefault فوری
FirstOrDefault فوری Skip با تأخیر
GroupBy با تأخیر SkipWhile با تأخیر
GroupJoin با تأخیر Sum فوری
Intersect با تأخیر Take با تأخیر
Join با تأخیر TakeWhile با تأخیر
Last فوری ThenBy با تأخیر
LastOrDefault فوری ThenByDescending با تأخیر
LongCount فوری ToArray فوری
Max فوری ToDictionary فوری
Min فوری ToList فوری
OfType با تأخیر ToLookup فوری
OrderBy با تأخیر Union با تأخیر
OrderByDescending با تأخیر Where با تأخیر

اگر به خاطر سپردن جدول بالا سخت است، می‌توانید فقط متدهایی را به خاطر بسپارید که استفاده از آنها باعث اجرای با تأخیر می‌شود. اگر نتیجه یک عبارت پرس و جو یک مقدار ساده مانند یک عدد و یا یک شئ باشد پس این متد از آن دسته‌ای است که باعث اجرای فوری پرس و جو می‌شوند. اما اگر نتیجه برگشتی از متد از نوع IEnumerable<T> باشد پس بدانید که بیشتر مواقع این متد از آن نوع دسته متدهایی است که از اجرای با تأخیر استفاده می‌کند.

یک روش برای تشخیص مقدار برگشتی IEnumerable<T> این است که با نشانگر ماوس بر روی کلمه کلیدی var مکث کنید. که در این صورت با باز شدن یک پنجره popup نوع IEnumerable<T> به شما نمایش داده می‌شود. اگر این نوع به شما نمایش داده شد بدانید که عبارت پرس و جو از اجرای با تأخیر استفاده می‌کند. مثلاً همانطور که در شکل زیر مشاهده می‌کنید از آنجاییکه متد ()Reverse از اجرای با تأخیر استفاده می‌کند، با مکث بر روی کلمه var پنجره popup نوع IEnumerable<T> را نشان می‌دهد :
deferred-execution-in-linq-01