مقدمه

در درس‌های گذشته با عملگرهای پایه‌ای LINQ مانند where، orderby و select آشنا شدیم. این‌ها ابزارهای اصلی برای شروع کار با LINQ هستند، اما کتابخانه‌ی LINQ بسیار غنی‌تر است و مجموعه‌ی گسترده‌ای از عملگرها را برای انجام کارهای پیچیده‌تر مانند گروه‌بندی، مرتب‌سازی چندسطحی، انجام محاسبات تجمعی و عملیات مجموعه‌ای فراهم می‌کند. این عملگرها که همگی به صورت متدهای بسطی بر روی IEnumerable<T> پیاده‌سازی شده‌اند، به ما قدرت می‌دهند تا تقریباً هر نوع کوئری داده‌ای را به شکلی خوانا و اعلانی بنویسیم. در این درس به بررسی برخی از این عملگرهای پرکاربرد خواهیم پرداخت.

داده‌های نمونه

برای نمایش بهتر عملگرها، از همان لیست کارمندان که در درس قبل ایجاد کردیم، استفاده می‌کنیم.

Copy Icon Program.cs
public record Employee(int Id, string Name, string Department, decimal Salary);

var employees = new List<Employee>
{
    new(1, "Sara", "HR", 55000m),
    new(2, "Ali", "Engineering", 80000m),
    new(3, "Mina", "Sales", 65000m),
    new(4, "Reza", "Engineering", 95000m),
    new(5, "Babak", "Sales", 70000m)
};

عملگرهای مرتب‌سازی پیشرفته

دیدیم که با OrderBy می‌توانیم یک دنباله را بر اساس یک معیار مرتب کنیم. اما اگر بخواهیم بر اساس چند معیار مرتب کنیم (مثلاً ابتدا بر اساس دپارتمان و سپس بر اساس حقوق) چه؟ برای این کار از متدهای ThenBy و ThenByDescending استفاده می‌کنیم.

Copy Icon Program.cs
// Sort by Department (ascending), then by Salary (descending) within each department.
var sortedEmployees = employees
    .OrderBy(e => e.Department)
    .ThenByDescending(e => e.Salary);

foreach (var emp in sortedEmployees)
{
    Console.WriteLine($"Dept: {emp.Department}, Name: {emp.Name}, Salary: {emp.Salary}");
}

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

عملگرهای گروه‌بندی (GroupBy)

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

Copy Icon Program.cs
// Group employees by their department.
var groups = employees.GroupBy(e => e.Department);

// The result is an IEnumerable of IGrouping
foreach (var group in groups)
{
    Console.WriteLine($"\n--- Department: {group.Key} ---"); // group.Key is the department name
    
    // Each group is itself a collection of employees.
    foreach (Employee employee in group)
    {
        Console.WriteLine($"  {employee.Name}");
    }
}

خروجی GroupBy مجموعه‌ای از گروه‌هاست. هر گروه یک Key دارد (که در اینجا نام دپارتمان است) و خود آن گروه یک کالکشن از تمام عناصری است که در آن گروه قرار گرفته‌اند.

عملگرهای انتخاب عنصر

این عملگرها برای انتخاب یک عنصر واحد از یک دنباله بر اساس یک شرط خاص به کار می‌روند و کوئری را بلافاصله اجرا می‌کنند.

  • First/FirstOrDefault: اولین عنصری که شرط را برآورده می‌کند، برمی‌گرداند. First در صورت عدم وجود عنصر، استثناء پرتاب می‌کند.
  • Single/SingleOrDefault: عنصری که شرط را برآورده می‌کند را برمی‌گرداند، اما اگر بیش از یک عنصر با شرط مطابقت داشته باشد، استثناء پرتاب می‌کند. این عملگر برای مواقعی که انتظار دارید نتیجه منحصر به فرد باشد (مانند جستجو بر اساس Id) بسیار مفید است.
  • Last/LastOrDefault: آخرین عنصری که شرط را برآورده می‌کند، برمی‌گرداند.
Copy Icon Program.cs
// Get the employee with Id = 3. We expect this to be unique.
Employee mina = employees.Single(e => e.Id == 3);

// Get the first employee in the "Sales" department.
Employee firstSales = employees.FirstOrDefault(e => e.Department == "Sales");

Console.WriteLine($"\nSingle employee with Id 3: {mina.Name}");
Console.WriteLine($"First sales person: {firstSales.Name}");

عملگرهای کمّی (Quantifiers)

این عملگرها یک مقدار bool برمی‌گردانند و برای بررسی وجود یا عدم وجود شرایطی در یک دنباله به کار می‌روند.

  • Any(): اگر حداقل یک عنصر شرط را برآورده کند، true برمی‌گرداند.
  • All(): اگر تمام عناصر شرط را برآورده کنند، true برمی‌گرداند.
Copy Icon Program.cs
// Check if there are ANY employees in the HR department.
bool hasHrEmployee = employees.Any(e => e.Department == "HR");
Console.WriteLine($"\nIs there any HR employee? {hasHrEmployee}"); // True

// Check if ALL employees earn more than 50000.
bool allPaidWell = employees.All(e => e.Salary > 50000);
Console.WriteLine($"Are all employees paid well? {allPaidWell}"); // True