در ادامه سری آموزشی LINQ به بررسی متغیرهای Range میپردازیم:
4 عنصر یک عبارت پرس و جو عبارتند از:• علملگرهای LINQ
• کلمات کلیدی Keyword
• متغیرهای Range
Range Variable : متغیر تعریف شدهی در یک محدوده خاص.
عبارت پرس و جوی زیر را در نظر بگیرد:
var query = from word in list where word.StartsWith("a") select word;
در این پرس و جو (from,in,where,select) کلمات کلیدی محسوب میشوند. البته where و select میتوانند به عنوان عملگر محسوب شوند.
شناسهی list یک متغیر محلی است و تنها موردی که باقی میماند شناسهی word است که به آن متغیر Range میگوییم. متغیرهای Range همانند متغیرهای مرسوم مورد استفادهی در برنامهها هستند که بصورت فقط خواندنی مهیا شده اند. با این اوصاف متغیرهای Range در ابتدا کمی عجیب به نظر میرسند. به این علت که در وسط عبارت پرس و جو معرفی میشوند و نیازی به تعریف شدن به روش مرسوم به شکل زیر را ندارند:
شناسهی list یک متغیر محلی است و تنها موردی که باقی میماند شناسهی word است که به آن متغیر Range میگوییم. متغیرهای Range همانند متغیرهای مرسوم مورد استفادهی در برنامهها هستند که بصورت فقط خواندنی مهیا شده اند. با این اوصاف متغیرهای Range در ابتدا کمی عجیب به نظر میرسند. به این علت که در وسط عبارت پرس و جو معرفی میشوند و نیازی به تعریف شدن به روش مرسوم به شکل زیر را ندارند:
String word;
در این حالت معرفی Word از طریق عبارت from انجام میشود. کامپایلر نوع دادهی متغیر را از طریق فرآیندی به نام Type Inference مشخص میکند. در مثال بالا کامپایلر تشخیص میدهد که Word از نوع string است؛ به این علت که جدا شده از <list<string میباشد.
متغیر word در دو حالت ممکن است قابل دسترس نباشد :
- پایان پرس و جو
- مواجه شدن با کلمه کلیدی into . این کلمهی کلیدی برای اتصال دو Query استفاده میشود.
نکته:در بعضی مواقع باید وضعیت متغیر Range را صریحا مشخص کنیم؛ بطور مثال کد زیر با خطا مواجه خواهد شد:
object[] ints = new object[] { 1, 2, 3 }; var query = from num in ints where num < 3 select num;
در زمان پردازش دستور Where، کامپایلر نمیتواند عملگر مقایسهای را برای یک نوع int و یک نوع object اجرا کند. برای حل این مشکل بصورت صریح (Explicit) نوع متغیر Range را مشخص میکنیم:
همانطور که میبینید در این حالت به کامپایلر اعلام میکنیم که num از نوع int میباشد و cast کردن با موفقیت انجام میشود و خروجی همان چیزی است که ما انتظار داریم.
تذکر : بهتر است از تعریف صریح متغیر Range پرهیز کنیم؛ مگر در شرایطی مثل کد بالا .
قطعه کد زیر بهراحتی کامپایل میشود و نیازی به اعلان صریح نوع متغیر range نیست. زیر از طریق مکانیزیم Type Inference نوع متغیر مشخص شده است.
همانطور که مشاهده میکنید کامپایلر خطای تعریف دو شناسهی یکتا را در یک محدوده، اعلام میکند.
تا اینجا از طریق کلمهی کلیدی from، متغیری را تعریف کردیم. با استفاده از کلمات کلیدی let ،into و join نیز میتوان متغیرهای Range تعریف کرد.
عبارت let
کلمهی کلیدی let این امکان را فراهم میکند تا یک متغیر Range جدید را ایجاد کرده و در عبارتهای بعدی از آن استفاده کنیم. در کد زیر از طریق کلمهی کلیدی let، یک متغیر Range جدید را بنام IsDairy تعریف میکنیم که از نوع bool میباشد:
متغیر isDairy در عبارت (clause) بعدی که where باشد، مورد استفاده قرار گرفته است. توجه داشته باشید که متغیر i تعریف شدهی در ابتدای پرس و جو، در بخش Select قابل دسترسی است. دستور let باعث از دسترس خارج شدن متغیر در بخش بعدی نمیشود.
در کد زیر قصد داریم عملیاتهای زیر را بر روی توالی ورودی اعمال کنیم:
1- جدا کردن عناصر توالی ورودی بر اساس جدا کنندهی " ,"
2- تبدیل همهی حروف عناصر توالی ایجاد شده به حروف بزرگ
3- جدا کردن عناصر توالی حاصل از مرحلهی 2، به شرط برابر بودن با MILK,BUTTER,CHEESE
4- نمایش توالی ایجاد شده
همانطور که مشاهده میکنید متغیر ایجاد شدهی توسط let میتواند یک مقدار عددی (مثال قبل) و یا یک مجموعه را در خود ذخیره کند (مثال فوق).
عبارت Into
متغیر جدیدی که توسط این دستور ایجاد میشود، میتواند نتیجهی حاصل از دستور Select را در خود ذخیره کند. در کد زیر یک نوع بینام ایجاد کرده و در ادامهی پرس و جو از آن استفاده میکنیم:
نکتهای که در ابتدای این بحث اشاره شد، در این مثال خود را نشان میدهد و آن هم عدم دسترسی به متغیر i در بخش پایانی پرس و جو (select نهایی) میباشد.
در ادامهی این سری آموزشی، به بررسی عبارت join میپردازیم.
var query = from int num in ints where num < 3 select num;
تذکر : بهتر است از تعریف صریح متغیر Range پرهیز کنیم؛ مگر در شرایطی مثل کد بالا .
قطعه کد زیر بهراحتی کامپایل میشود و نیازی به اعلان صریح نوع متغیر range نیست. زیر از طریق مکانیزیم Type Inference نوع متغیر مشخص شده است.
List<string> list = new List<string> {"LINQ","Query","adventure"}; var query = from string word in list where word.Contains("r") orderby word ascending select word;
اعلان صریح متغیر Range باعث میشود که پشت پرده، عملیات <Cast<T اتفاق بیافتد و در مواقع غیر ضروری مثل کد فوق ممکن است کارآیی را کاهش دهد. یکی از نقاطی که در صورت پایین بودن کارآیی دستورات LINQ باید بررسی شود همین مورد CAST است. البته تنها استثنایی که در این مورد وجود دارد، توالیهای غیر جنریک هستند (non generic Enumerable). در این حالت باید از Cast استفاده کرد.
متغیر Range در محدودهی مورد استفاده باید از یک شناسهی یکتا برخوردار باشد. string word="test"; List<string> list = new List<string> {"LINQ","Query","adventure"}; var query = from string word in list where word.Contains("r") orderby word ascending select word;
تا اینجا از طریق کلمهی کلیدی from، متغیری را تعریف کردیم. با استفاده از کلمات کلیدی let ،into و join نیز میتوان متغیرهای Range تعریف کرد.
عبارت let
کلمهی کلیدی let این امکان را فراهم میکند تا یک متغیر Range جدید را ایجاد کرده و در عبارتهای بعدی از آن استفاده کنیم. در کد زیر از طریق کلمهی کلیدی let، یک متغیر Range جدید را بنام IsDairy تعریف میکنیم که از نوع bool میباشد:
Ingredient[] ingredients = { new Ingredient {Name = "Sugar", Calories = 500}, new Ingredient {Name = "Egg", Calories = 100}, new Ingredient {Name = "Milk", Calories = 150}, new Ingredient {Name = "Flour", Calories = 50}, new Ingredient {Name = "Butter", Calories = 200} }; IEnumerable<Ingredient> highCalDairyQuery = from i in ingredients let isDairy = i.Name == "Milk" || i.Name == "Butter" where i.Calories >= 150 && isDairy select i; foreach (var ingredient in highCalDairyQuery) { Console.WriteLine(ingredient.Name); }
در کد زیر قصد داریم عملیاتهای زیر را بر روی توالی ورودی اعمال کنیم:
1- جدا کردن عناصر توالی ورودی بر اساس جدا کنندهی " ,"
2- تبدیل همهی حروف عناصر توالی ایجاد شده به حروف بزرگ
3- جدا کردن عناصر توالی حاصل از مرحلهی 2، به شرط برابر بودن با MILK,BUTTER,CHEESE
4- نمایش توالی ایجاد شده
string[] csvRecipes = { "milk,sugar,eggs", "flour,BUTTER,eggs", "vanilla,ChEEsE,oats" }; var dairyQuery = from csvRecipe in csvRecipes let ingredients = csvRecipe.Split(',') from ingredient in ingredients let uppercaseIngredient = ingredient.ToUpper() where uppercaseIngredient == "MILK" || uppercaseIngredient == "BUTTER" || uppercaseIngredient == "CHEESE" select uppercaseIngredient; foreach (var dairyIngredient in dairyQuery) { Console.WriteLine($"{dairyIngredient} is dairy"); }
عبارت Into
متغیر جدیدی که توسط این دستور ایجاد میشود، میتواند نتیجهی حاصل از دستور Select را در خود ذخیره کند. در کد زیر یک نوع بینام ایجاد کرده و در ادامهی پرس و جو از آن استفاده میکنیم:
Ingredient[] ingredients = { new Ingredient {Name = "Sugar", Calories = 500}, new Ingredient {Name = "Egg", Calories = 100}, new Ingredient {Name = "Milk", Calories = 150}, new Ingredient {Name = "Flour", Calories = 50}, new Ingredient {Name = "Butter", Calories = 200} }; IEnumerable<Ingredient> highCalDairyQuery = from i in ingredients select new //نوع بی نام { OriginalIngredient = i, IsDairy = i.Name == "Milk" || i.Name == "Butter", IsHighCalorie = i.Calories >= 150 } into temp where temp.IsDairy && temp.IsHighCalorie select temp.OriginalIngredient; foreach (var ingredient in highCalDairyQuery) { Console.WriteLine(ingredient.Name); }
در ادامهی این سری آموزشی، به بررسی عبارت join میپردازیم.