مقدمه
به فصل سیزدهم خوش آمدید! در این فصل به بررسی یکی از انقلابیترین و قدرتمندترین ویژگیهای
.NET یعنی LINQ (Language-Integrated Query) خواهیم پرداخت. LINQ مجموعهای از تکنولوژیهاست که به ما اجازه میدهد
با استفاده از یک سینتکس یکپارچه و شبیه به SQL، دادهها را از منابع مختلفی مانند
کالکشنها، آرایهها، فایلهای XML و پایگاههای داده، کوئری و دستکاری کنیم. اما
LINQ جادو نیست؛ بلکه حاصل ترکیب هوشمندانهی چندین ویژگی زبان C# است که ما
در فصلهای گذشته با آنها آشنا شدهایم. در این درس، مروری بر این ساختارهای کلیدی خواهیم داشت تا
ببینیم چگونه با هم کار میکنند تا قدرت LINQ را ممکن سازند.
اجزای سازندهی LINQ
LINQ بر پایهی چندین ویژگی زبان بنا شده است. درک نقش هر کدام از این ویژگیها به شما
کمک میکند تا نحوهی کار LINQ در پشت صحنه را بهتر درک کنید.
متغیرهای محلی با نوع ضمنی (var)
کوئریهای LINQ اغلب نتایجی را تولید میکنند که نوع آنها پیچیده است یا حتی از یک نوع
ناشناس (که در ادامه میبینیم) تشکیل شده است. نوشتن نام کامل این نوعها میتواند طولانی و
خستهکننده باشد. کلمهی کلیدی var به ما اجازه میدهد تا تعریف متغیر
را به کامپایلر بسپاریم. کامپایلر به طور خودکار نوع صحیح را استنباط میکند و کد ما بسیار تمیزتر و
خواناتر باقی میماند. تقریباً در تمام مثالهای LINQ از var برای ذخیرهی نتایج
کوئری
استفاده میشود.
متدهای بسطی (Extension Methods)
این ویژگی، ستون فقرات سینتکس متدی LINQ است. تمام عملگرهای استاندارد کوئری مانند
Where، Select، OrderBy و... در واقع متدهای بسطی هستند که برای اینترفیس IEnumerable<T> تعریف شدهاند. این بدان معناست که هر کالکشنی که
این اینترفیس را پیادهسازی کند (مانند List<T> و آرایهها)، به
طور خودکار به تمام این متدهای
قدرتمند دسترسی پیدا میکند.
عبارات لامبدا (Lambda Expressions)
عبارات لامبدا "سوخت" لازم برای متدهای بسطی LINQ را فراهم میکنند. متدهایی مانند
Where و Select یک نماینده (معمولاً از نوع Func) به عنوان پارامتر میپذیرند
تا منطق فیلتر
کردن یا پرتاب کردن را مشخص کنند. عبارات لامبدا مختصرترین و خواناترین راه برای ارائهی این منطق
به صورت درونخطی هستند.
Program.cs
int[] numbers = { 1, 2, 3, 4, 5 };
var evenNumbers = numbers.Where(n => n % 2 == 0);
نوعهای ناشناس (Anonymous Types)
اغلب اوقات، ما به تمام دادههای یک شیء در نتیجهی کوئری خود نیاز نداریم. انواع ناشناس به ما
اجازه میدهند تا با استفاده از select new { ... } یک نوع دادهی سبک
و موقتی بسازیم که فقط
شامل پراپرتیهای مورد نیاز ماست. این کار به خصوص در پرتاب (projection) دادهها بسیار مفید است و
از ساختن کلاسهای اضافی جلوگیری میکند.
ترکیب همه اجزا: یک کوئری LINQ
حالا بیایید ببینیم چگونه تمام این ویژگیها در یک کوئری LINQ واقعی با هم ترکیب
میشوند. فرض کنید لیستی از محصولات داریم و میخواهیم نام محصولاتی را که قیمتشان کمتر از 50 واحد
است، پیدا کرده و بر اساس نام مرتب کنیم.
Program.cs
public record Product(string Name, decimal Price);
var products = new List<Product>
{
new("Keyboard", 75m),
new("Mouse", 25m),
new("Monitor", 300m),
new("Webcam", 45m)
};
var cheapProductInfo = products
.Where(p => p.Price < 50)
.OrderBy(p => p.Name)
.Select(p => new { p.Name });
Console.WriteLine("Cheap products:");
foreach (var product in cheapProductInfo)
{
Console.WriteLine(product.Name);
}
خروجی این کد به شکل زیر خواهد بود:
Cheap products:
Mouse
Webcam
در این مثال واحد، ما از var برای نگهداری نتیجه، از متدهای بسطی Where، OrderBy و Select،
از عبارات لامبدا برای تعریف منطق آنها و از یک نوع ناشناس برای پرتاب نتیجهی نهایی استفاده
کردهایم.
نگاهی به آینده: دو سینتکس برای LINQ
LINQ را میتوان با دو سینتکس متفاوت نوشت: سینتکس متد (Method Syntax)
که در مثال بالا دیدیم و از زنجیرهی متدهای بسطی استفاده میکند، و سینتکس کوئری (Query
Syntax) که شبیه به SQL است.
var querySyntaxResult = from p in products
where p.Price < 50
orderby p.Name
select new { p.Name };
هر دو سینتکس در نهایت توسط کامپایلر به یک کد مشابه تبدیل میشوند و انتخاب بین آنها اغلب به
سلیقه و خوانایی بستگی دارد. در درسهای آینده، هر دو سینتکس و عملگرهای مختلف LINQ را
به تفصیل بررسی خواهیم کرد.