مقدمه

در فصل گذشته، دیدیم که برای نوشتن کوئری‌های LINQ می‌توان از دو سینتکس متفاوت استفاده کرد: سینتکس کوئری (Query Syntax) که شبیه به SQL است، و سینتکس متد (Method Syntax) که از زنجیره‌ی متدهای بسطی استفاده می‌کند. ما اشاره کردیم که این دو سینتکس در نهایت معادل یکدیگر هستند. اما این به چه معناست؟ در این درس، پرده از این "جادو" برداشته و نشان می‌دهیم که چگونه کامپایلر C#، سینتکس خوانا و اعلانی کوئری را به فراخوانی‌های استاندارد متدهای بسطی ترجمه می‌کند. درک این فرآیند به شما کمک می‌کند تا به درک عمیق‌تری از نحوه‌ی کار LINQ برسید و بفهمید که LINQ یک زبان جداگانه نیست، بلکه یکپارچگی هوشمندانه‌ی ویژگی‌های زبان C# است.

سینتکس کوئری: یک "شکر نحوی"

در برنامه‌نویسی، اصطلاح شکر نحوی (Syntactic Sugar) به سینتکسی در یک زبان گفته می‌شود که برای ساده‌تر یا خواناتر کردن یک کار طراحی شده، اما هیچ قابلیت جدیدی به زبان اضافه نمی‌کند. در واقع، کامپایلر این سینتکس "شیرین" را به یک ساختار بنیادی‌تر و معادل ترجمه می‌کند.

سینتکس کوئری LINQ یک مثال کلاسیک از شکر نحوی است. تمام عبارت‌هایی که با from ... in ... where ... select می‌نویسید، قبل از کامپایل نهایی، توسط کامپایلر C# به زنجیره‌ای از فراخوانی متدهای بسطی (سینتکس متد) تبدیل می‌شوند.

فرآیند ترجمه

بیایید ببینیم هر بخش از یک سینتکس کوئری به کدام متد بسطی ترجمه می‌شود:

  • عبارت from item in collection، منبع داده را مشخص می‌کند.
  • عبارت where condition مستقیماً به فراخوانی متد Where(item => condition) ترجمه می‌شود.
  • عبارت orderby key به فراخوانی متد OrderBy(item => key) (و برای descending به OrderByDescending(...)) ترجمه می‌شود.
  • عبارت select expression مستقیماً به فراخوانی متد Select(item => expression) ترجمه می‌شود.
  • عبارت group item by key به فراخوانی متد GroupBy(item => key) ترجمه می‌شود.

یک مثال عینی

بیایید یک کوئری کامل را در نظر بگیریم و ببینیم کامپایلر دقیقاً چه کاری با آن انجام می‌دهد. ما از همان لیست کارمندان درس قبل استفاده می‌کنیم.

قدم اول: کدی که ما می‌نویسیم (سینتکس کوئری)

فرض کنید می‌خواهیم نام کارمندان دپارتمان "Engineering" را که حقوقی بیش از ۷۵۰۰۰ دارند، پیدا کرده و بر اساس نامشان مرتب کنیم. ما این کوئری را به صورت زیر می‌نویسیم:

Copy Icon Program.cs
// The human-readable query we write.
var querySyntax = from emp in employees
                      where emp.Department == "Engineering" && emp.Salary > 75000
                      orderby emp.Name
                      select emp.Name;

قدم دوم: کدی که کامپایلر تولید می‌کند (سینتکس متد)

وقتی کامپایلر این کد را می‌بیند، آن را به طور کامل به سینتکس متد معادل آن ترجمه می‌کند. کدی که واقعاً کامپایل و اجرا می‌شود، به شکل زیر است:

Copy Icon Compiler Translation
// What the compiler generates behind the scenes.
var methodSyntax = employees
    .Where(emp => emp.Department == "Engineering" && emp.Salary > 75000)
    .OrderBy(emp => emp.Name)
    .Select(emp => emp.Name);

همانطور که می‌بینید، نتیجه‌ی نهایی هر دو کد دقیقاً یکسان است. سینتکس کوئری تنها یک راه ساده‌تر و خواناتر برای بیان همان زنجیره‌ی فراخوانی متدهاست.

چرا دو سینتکس وجود دارد و از کدام استفاده کنیم؟

این سوال پیش می‌آید که اگر هر دو سینتکس یک کار را انجام می‌دهند، چرا هر دو وجود دارند؟

  • سینتکس کوئری (Query Syntax): برای توسعه‌دهندگانی که با SQL آشنا هستند، بسیار شهودی و خواناست. این سینتکس برای کوئری‌های ساده تا متوسط که شامل فیلتر، مرتب‌سازی و پرتاب هستند، عالی عمل می‌کند.
  • سینتکس متد (Method Syntax): این سینتکس قدرتمندتر و انعطاف‌پذیرتر است. اولاً، تمام عملگرهای LINQ (مانند Count, Take, FirstOrDefault, Max) معادل کلمه‌ی کلیدی در سینتکس کوئری را ندارند. برای استفاده از این عملگرها، شما باید از سینتکس متد استفاده کنید. ثانیاً، زنجیره‌سازی متدها برای ساخت دینامیک کوئری‌ها در کد، ساده‌تر است.

رویکرد ترکیبی: در عمل، بسیاری از برنامه‌نویسان از یک رویکرد ترکیبی استفاده می‌کنند. آن‌ها بخش اصلی کوئری را با سینتکس کوئری می‌نویسند و سپس نتیجه را در پرانتز قرار داده و یک عملگر از سینتکس متد را بر روی آن فراخوانی می‌کنند.

// A hybrid approach
int count = (from emp in employees
             where emp.Department == "Sales"
             select emp).Count();

در نهایت، انتخاب بین این دو سینتکس به سلیقه‌ی شخصی و خوانایی کد در آن سناریوی خاص بستگی دارد. مهم این است که بدانید هر دو در نهایت به یک چیز تبدیل می‌شوند.