در ادامه سری آموزشی 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 در ابتدا کمی عجیب به نظر میرسند. به این علت که در وسط عبارت پرس و جو معرفی میشوند و نیازی به تعریف شدن به روش مرسوم به شکل زیر را ندارند:
در این حالت معرفی 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 را مشخص میکنیم:
var query = from int num in ints
where num < 3
select num;
همانطور که میبینید در این حالت به کامپایلر اعلام میکنیم که num از نوع int میباشد و cast کردن با موفقیت انجام میشود و خروجی همان چیزی است که ما انتظار داریم.
تذکر : بهتر است از تعریف صریح متغیر 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);
}
متغیر isDairy در عبارت (clause) بعدی که where باشد، مورد استفاده قرار گرفته است. توجه داشته باشید که متغیر i تعریف شدهی در ابتدای پرس و جو، در بخش Select قابل دسترسی است.
دستور let باعث از دسترس خارج شدن متغیر در بخش بعدی نمیشود.
در کد زیر قصد داریم عملیاتهای زیر را بر روی توالی ورودی اعمال کنیم:
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");
}
همانطور که مشاهده میکنید متغیر ایجاد شدهی توسط let میتواند یک مقدار عددی (مثال قبل) و یا یک مجموعه را در خود ذخیره کند (مثال فوق).
عبارت 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);
}
نکتهای که در ابتدای این بحث اشاره شد، در این مثال خود را نشان میدهد و آن هم عدم دسترسی به متغیر
i در بخش پایانی پرس و جو (select نهایی) میباشد.
در ادامهی این سری آموزشی، به بررسی عبارت join میپردازیم.