خیلی خوب، پس شروع میکنیم درس چهاردهم – LINQ قسمت اول: آشنایی با LINQ و متدهای پایه.
یک پروژه جدید کنسولی بسازید (dotnet new console) و کدها را همراه من بنویسید و اجرا کنید.
---
📘 درس چهاردهم – LINQ قسمت 1: آشنایی با LINQ
🎯 سرفصلها:
1. LINQ چیست و چرا نیاز داریم؟
2. دو روش نوشتن LINQ (Query Syntax و Method Syntax)
3. متد Where (فیلتر کردن)
4. متد Select (انتخاب و تبدیل)
5. متدهای First، FirstOrDefault، Single، SingleOrDefault
6. متد ToList و اجرای دیررس (Deferred Execution)
7. تمرین جامع
---
⏱️ قسمت 1: LINQ چیست؟
LINQ = Language Integrated Query
زبانی برای پرس و جو از دادهها درون خود سیشارپ.
بدون LINQ (کد قدیمی و طولانی):
csharp
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
List<int> evens = new List<int>();
foreach (int n in numbers)
{
if (n % 2 == 0)
{
evens.Add(n);
}
}
با LINQ (کد کوتاه و خواناتر):
csharp
using System.Linq; // اضافه کردن این using لازم است
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var evens = numbers.Where(n => n % 2 == 0).ToList();
📌 نکته: حتماً using System.Linq; را در بالای فایل اضافه کنید.
---
⏱️ قسمت 2: دو روش نوشتن LINQ
روش اول: Method Syntax (روش متدی) – رایجتر
csharp
var result = numbers.Where(n => n > 5).Select(n => n * 2);
روش دوم: Query Syntax (روش شبه SQL) – شبیه به SQL
csharp
var result = from n in numbers
where n > 5
select n * 2;
مقایسه هر دو روش:
csharp
int[] scores = { 85, 92, 78, 90, 88, 70, 95 };
// Method Syntax
var highScores1 = scores.Where(s => s >= 90).OrderBy(s => s);
// Query Syntax
var highScores2 = from s in scores
where s >= 90
orderby s
select s;
Console.WriteLine("High scores (>=90):");
foreach (var score in highScores1)
{
Console.Write(score + " "); // 90 92 95
}
📌 نکته: هر دو روش نتیجه یکسان دارند. Method Syntax پرکاربردتر است.
---
⏱️ قسمت 3: متد Where (فیلتر کردن)
Where برای فیلتر کردن دادهها بر اساس یک شرط استفاده میشود.
csharp
List<string> names = new List<string>
{
"Ali", "Reza", "Sara", "Mohammad", "Neda", "Hossein"
};
// نامهایی که با "A" شروع میشوند
var startsWithA = names.Where(n => n.StartsWith("A"));
Console.WriteLine("Starts with A: " + string.Join(", ", startsWithA));
// نامهایی که طول آنها بیشتر از 4 است
var longNames = names.Where(n => n.Length > 4);
Console.WriteLine("Long names: " + string.Join(", ", longNames));
// ترکیب چند شرط
var result = names.Where(n => n.Length > 3 && n.Contains("e"));
Console.WriteLine("Long names with 'e': " + string.Join(", ", result));
---
⏱️ قسمت 4: متد Select (انتخاب و تبدیل)
Select برای تبدیل هر عنصر به شکل دیگری استفاده میشود.
csharp
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
// تبدیل هر عدد به مربع آن
var squares = numbers.Select(n => n * n);
Console.WriteLine("Squares: " + string.Join(", ", squares)); // 1,4,9,16,25
// تبدیل هر عدد به متن
var textNumbers = numbers.Select(n => $"Number: {n}");
foreach (var item in textNumbers)
{
Console.WriteLine(item);
}
مثال با کلاس Person:
csharp
class Person
{
public string Name { get; set; }
public int Age { get; set; }
public string City { get; set; }
}
List<Person> people = new List<Person>
{
new Person { Name = "Ali", Age = 25, City = "Tehran" },
new Person { Name = "Sara", Age = 30, City = "Shiraz" },
new Person { Name = "Reza", Age = 22, City = "Tehran" },
new Person { Name = "Neda", Age = 28, City = "Isfahan" }
};
// فقط نامها را انتخاب کن
var namesOnly = people.Select(p => p.Name);
Console.WriteLine("Names: " + string.Join(", ", namesOnly));
// نام و سن را به صورت یک شیء ناشناس (Anonymous) انتخاب کن
var nameAndAge = people.Select(p => new { p.Name, p.Age });
foreach (var item in nameAndAge)
{
Console.WriteLine($"{item.Name} is {item.Age} years old");
}
---
⏱️ قسمت 5: ترکیب Where و Select
`csharp List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// اعداد زوج را گرفته و آنها را ۲ برابر کن
var result = numbers
.Where(n => n % 2 == 0) // 2,4,6,8,10
.Select(n => n * 2); // 4,8,12,16,20
Console.WriteLine("Even numbers doubled: " + string.Join(", ", result));
// با Query Syntax
var result2 = from n in numbers
where n % 2 == 0
select n * 2;
---
⏱️ قسمت 6: متدهای First، FirstOrDefault، Single
متد توضیح
First() اولین عنصر را برمیگرداند (اگر نباشد خطا میدهد)
FirstOrDefault() اولین عنصر را برمیگرداند (اگر نباشد null میدهد)
Single() فقط یک عنصر باید باشد (اگر بیشتر یا کمتر باشد خطا)
SingleOrDefault() فقط یک عنصر باید باشد (اگر کمتر باشد null)
csharp
List<int> numbers = new List<int> { 10, 20, 30, 40, 50 };
int first = numbers.First();
Console.WriteLine($"First: {first}"); // 10
int firstGreaterThan25 = numbers.First(n => n > 25);
Console.WriteLine($"First > 25: {firstGreaterThan25}"); // 30
int firstOrDefault = numbers.FirstOrDefault(n => n > 100);
Console.WriteLine($"First > 100: {firstOrDefault}"); // 0 (default)
List<int> emptyList = new List<int>();
int safeResult = emptyList.FirstOrDefault();
Console.WriteLine($"FirstOrDefault on empty: {safeResult}"); // 0
مثال با اشیاء:
csharp
List<Person> people = new List<Person>
{
new Person { Name = "Ali", Age = 25, City = "Tehran" },
new Person { Name = "Sara", Age = 30, City = "Shiraz" }
};
// پیدا کردن شخص با نام خاص
Person? person = people.FirstOrDefault(p => p.Name == "Ali");
if (person != null)
{
Console.WriteLine($"Found: {person.Name}, {person.Age}");
}
// Single - اگر بیش از یک عنصر باشد خطا میدهد
try
{
Person single = people.Single(p => p.City == "Tehran"); // فقط یک نفر از تهران
Console.WriteLine($"Single from Tehran: {single.Name}");
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
---
⏱️ قسمت 7: متد ToList و اجرای دیررس
نکته مهم: اکثر متدهای LINQ اجرای دیررس (Deferred Execution) دارند. یعنی تا زمانی که به نتیجه نیاز نباشد، اجرا نمیشوند.
csharp
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
// اینجا هنوز اجرا نشده
var query = numbers.Where(n => n > 2);
// حالا که با ToList تبدیل میکنیم، اجرا میشود
var result1 = query.ToList();
// یا وقتی در حلقه استفاده میکنیم، اجرا میشود
foreach (var n in query) // اینجا اجرا میشود
{
Console.WriteLine(n);
}
تفاوت اجرای دیررس و فوری:
csharp
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
// اجرای دیررس (Deferred)
var deferred = numbers.Where(n => n > 2);
// اجرای فوری (Immediate)
var immediate = numbers.Where(n => n > 2).ToList();
// حالا عدد جدید اضافه میکنیم
numbers.Add(10);
// deferred دوباره اجرا میشود و عدد 10 را هم میبیند
Console.WriteLine("Deferred: " + string.Join(", ", deferred)); // 3,4,5,10
// immediate قبلاً اجرا شده و تغییر نمیکند
Console.WriteLine("Immediate: " + string.Join(", ", immediate)); // 3,4,5
---
✅ تمرین نهایی درس چهاردهم
برنامهای بنویسید که:
1. لیستی از محصولات (Product) با Name، Price، Category بسازد
2. محصولات گرانتر از 100,000 تومان را پیدا کند
3. فقط نام محصولات را نمایش دهد
4. اولین محصول از دسته "Electronics" را پیدا کند
5. مجموع قیمت همه محصولات را محاسبه کند
csharp
using System;
using System.Collections.Generic;
using System.Linq;
class Product
{
public string Name { get; set; }
public decimal Price { get; set; }
public string Category { get; set; }
}
class Program
{
static void Main()
{
List<Product> products = new List<Product>
{
new Product { Name = "Laptop", Price = 15000000, Category = "Electronics" },
new Product { Name = "Mouse", Price = 250000, Category = "Electronics" },
new Product { Name = "Book", Price = 85000, Category = "Books" },
new Product { Name = "Pen", Price = 15000, Category = "Stationery" },
new Product { Name = "Keyboard", Price = 850000, Category = "Electronics" },
new Product { Name = "Notebook", Price = 45000, Category = "Stationery" },
new Product { Name = "Phone", Price = 12000000, Category = "Electronics" }
};
Console.WriteLine("=== Product List ===");
foreach (var p in products)
{
Console.WriteLine($"{p.Name}: {p.Price:C} ({p.Category})");
}
// 1. محصولات گرانتر از 100,000 تومان
var expensive = products.Where(p => p.Price > 100000);
Console.WriteLine("\n=== Products > 100,000 ===");
foreach (var p in expensive)
{
Console.WriteLine($"{p.Name}: {p.Price:C}");
}
// 2. فقط نام محصولات گرانقیمت
var expensiveNames = products
.Where(p => p.Price > 100000)
.Select(p => p.Name);
Console.WriteLine("\n=== Names of expensive products ===");
Console.WriteLine(string.Join(", ", expensiveNames));
// 3. اولین محصول از دسته Electronics
var firstElectronic = products.FirstOrDefault(p => p.Category == "Electronics");
if (firstElectronic != null)
{
Console.WriteLine($"\n=== First Electronic Product ===");
Console.WriteLine($"{firstElectronic.Name} - {firstElectronic.Price:C}");
}
// 4. مجموع قیمت همه محصولات
decimal totalPrice = products.Sum(p => p.Price);
Console.WriteLine($"\n=== Total Inventory Value ===");
Console.WriteLine($"{totalPrice:C}");
// 5. محصولات الکترونیک با قیمت کمتر از 1,000,000
var budgetElectronics = products
.Where(p => p.Category == "Electronics" && p.Price < 1000000)
.Select(p => p.Name);
Console.WriteLine("\n=== Budget Electronics (< 1,000,000) ===");
Console.WriteLine(string.Join(", ", budgetElectronics));
Console.WriteLine("\nPress Enter to exit...");
Console.ReadLine();
}
}
`--- 📌 جمعبندی درس چهاردهم متد کاربرد مثال Where فیلتر کردن .Where(x => x > 5) Select تبدیل کردن .Select(x => x * 2) First اولین عنصر .First() FirstOrDefault اولین عنصر (امن) .FirstOrDefault() Single فقط یک عنصر .Single(x => x.Id == 5) ToList تبدیل به List .ToList() نکات کلیدی: · حتماً using System.Linq; را اضافه کنید · متدهای LINQ اکثراً اجرای دیررس دارند · Method Syntax رایجتر از Query Syntax است --- 🧪 تمرین برای شما 1. لیستی از اعداد بسازید و اعداد بخشپذیر بر 3 را پیدا کنید 2. لیستی از رشتهها بسازید و رشتههایی که طول آنها بیشتر از 5 است را با حروف بزرگ نمایش دهید 3. با کلاس Student (Name, Score, Grade) لیستی بسازید و دانشجویانی که نمره بالای 17 دارند را پیدا کنید --- اگر این درس را کامل فهمیدید، بگویید تا قسمت دوم LINQ (مرتبسازی و گروهبندی) را شروع کنیم. سوالی بود بپرسید.
خیلی خوب، رسیدیم به قسمت دوم LINQ (درس پانزدهم) – مرتبسازی، گروهبندی و محاسبات.
یک پروژه جدید بسازید (dotnet new console) و کدها را همراه من بنویسید و اجرا کنید.
---
📘 درس پانزدهم – LINQ قسمت 2: مرتبسازی، گروهبندی و محاسبات
🎯 سرفصلها:
1. مرتبسازی (OrderBy, OrderByDescending, ThenBy)
2. عملگرهای شرطی (Any, All, Contains)
3. گروهبندی (GroupBy)
4. محاسبات آماری (Sum, Average, Count, Min, Max)
5. متدهای Take, Skip
6. تمرین جامع
---
⏱️ قسمت 1: مرتبسازی (OrderBy, OrderByDescending)
csharp
using System;
using System.Collections.Generic;
using System.Linq;
class Student
{
public string Name { get; set; }
public int Age { get; set; }
public double Score { get; set; }
public string City { get; set; }
}
class Program
{
static void Main()
{
List<Student> students = new List<Student>
{
new Student { Name = "Ali", Age = 20, Score = 18.5, City = "Tehran" },
new Student { Name = "Sara", Age = 22, Score = 19.0, City = "Shiraz" },
new Student { Name = "Reza", Age = 19, Score = 17.5, City = "Tehran" },
new Student { Name = "Neda", Age = 21, Score = 20.0, City = "Isfahan" },
new Student { Name = "Hossein", Age = 23, Score = 16.0, City = "Shiraz" },
new Student { Name = "Zahra", Age = 20, Score = 18.0, City = "Tehran" }
};
// مرتبسازی صعودی بر اساس نمره
var byScoreAsc = students.OrderBy(s => s.Score);
Console.WriteLine("=== Ordered by Score (Ascending) ===");
foreach (var s in byScoreAsc)
{
Console.WriteLine($"{s.Name}: {s.Score}");
}
// مرتبسازی نزولی بر اساس نمره
var byScoreDesc = students.OrderByDescending(s => s.Score);
Console.WriteLine("\n=== Ordered by Score (Descending) ===");
foreach (var s in byScoreDesc)
{
Console.WriteLine($"{s.Name}: {s.Score}");
}
// مرتبسازی بر اساس سن (صعودی)
var byAge = students.OrderBy(s => s.Age);
Console.WriteLine("\n=== Ordered by Age ===");
foreach (var s in byAge)
{
Console.WriteLine($"{s.Name}: {s.Age} years old");
}
}
}
---
⏱️ قسمت 2: مرتبسازی چند سطحی (ThenBy)
csharp
// مرتبسازی اول بر اساس شهر، سپس بر اساس نمره
var byCityThenScore = students
.OrderBy(s => s.City)
.ThenByDescending(s => s.Score);
Console.WriteLine("=== Ordered by City, then by Score ===");
foreach (var s in byCityThenScore)
{
Console.WriteLine($"{s.City} - {s.Name}: {s.Score}");
}
// خروجی:
// Isfahan - Neda: 20
// Shiraz - Sara: 19
// Shiraz - Hossein: 16
// Tehran - Sara: 18.5
// Tehran - Zahra: 18
// Tehran - Reza: 17.5
// مرتبسازی سه سطحی
var multiSort = students
.OrderBy(s => s.City)
.ThenBy(s => s.Age)
.ThenByDescending(s => s.Score);
Console.WriteLine("\n=== Three-level sorting ===");
foreach (var s in multiSort)
{
Console.WriteLine($"{s.City} - Age:{s.Age} - {s.Name}: {s.Score}");
}
---
⏱️ قسمت 3: عملگرهای شرطی (Any, All, Contains)
متد کاربرد خروجی
Any() آیا حداقل یک عنصر وجود دارد؟ bool
Any(شرط) آیا حداقل یک عنصر شرط را دارد؟ bool
All(شرط) آیا همه عناصر شرط را دارند؟ bool
Contains(مقدار) آیا مقدار مشخصی وجود دارد؟ bool
`csharp // Any - بررسی وجود حداقل یک عنصر bool hasAnyStudent = students.Any(); Console.WriteLine($"Has any student? {hasAnyStudent}"); // True bool hasTehrani = students.Any(s => s.City == "Tehran"); Console.WriteLine($"Has student from Tehran? {hasTehrani}"); // True bool hasStudentWithScore20 = students.Any(s => s.Score == 20); Console.WriteLine($"Has student with score 20? {hasStudentWithScore20}"); // True // All - بررسی همه عناصر bool allHaveName = students.All(s => !string.IsNullOrEmpty(s.Name)); Console.WriteLine($"All students have names? {allHaveName}"); // True bool allPassed = students.All(s => s.Score >= 10); Console.WriteLine($"All students passed? {allPassed}"); // True bool allAbove18 = students.All(s => s.Age >= 18); Console.WriteLine($"All students are above 18? {allAbove18}"); // True
// Contains
List<string> cities = students.Select(s => s.City).ToList();
bool hasShiraz = cities.Contains("Shiraz");
Console.WriteLine($"Contains Shiraz? {hasShiraz}"); // True
---
⏱️ قسمت 4: گروهبندی (GroupBy)
csharp
// گروهبندی دانشجویان بر اساس شهر
var groupsByCity = students.GroupBy(s => s.City);
Console.WriteLine("=== Students Grouped by City ===");
foreach (var group in groupsByCity)
{
Console.WriteLine($"\nCity: {group.Key}");
Console.WriteLine($"Count: {group.Count()}");
foreach (var student in group)
{
Console.WriteLine($" - {student.Name}: Score {student.Score}");
}
}
// گروهبندی با محاسبه میانگین هر گروه
var cityStats = students
.GroupBy(s => s.City)
.Select(g => new
{
City = g.Key,
Count = g.Count(),
AverageScore = g.Average(s => s.Score),
MaxScore = g.Max(s => s.Score),
MinScore = g.Min(s => s.Score)
});
Console.WriteLine("\n=== City Statistics ===");
foreach (var stat in cityStats)
{
Console.WriteLine($"{stat.City}: {stat.Count} students, Avg: {stat.AverageScore:F2}, Max: {stat.MaxScore}, Min: {stat.MinScore}");
}
---
⏱️ قسمت 5: محاسبات آماری (Sum, Average, Count, Min, Max)
csharp
// محاسبات روی همه دانشجویان
double totalScore = students.Sum(s => s.Score);
double averageScore = students.Average(s => s.Score);
double maxScore = students.Max(s => s.Score);
double minScore = students.Min(s => s.Score);
int studentCount = students.Count();
Console.WriteLine("=== Overall Statistics ===");
Console.WriteLine($"Total Students: {studentCount}");
Console.WriteLine($"Total Score: {totalScore}");
Console.WriteLine($"Average Score: {averageScore:F2}");
Console.WriteLine($"Highest Score: {maxScore}");
Console.WriteLine($"Lowest Score: {minScore}");
// محاسبات با شرط
double tehranAverage = students
.Where(s => s.City == "Tehran")
.Average(s => s.Score);
int above18Count = students.Count(s => s.Age > 18);
double maxInTehran = students
.Where(s => s.City == "Tehran")
.Max(s => s.Score);
Console.WriteLine($"\nTehran Average: {tehranAverage:F2}");
Console.WriteLine($"Students above 18: {above18Count}");
Console.WriteLine($"Max score in Tehran: {maxInTehran}");
---
⏱️ قسمت 6: متدهای Take و Skip (صفحهبندی)
csharp
// Take - گرفتن تعدادی از ابتدا
var top3 = students.OrderByDescending(s => s.Score).Take(3);
Console.WriteLine("=== Top 3 Students ===");
foreach (var s in top3)
{
Console.WriteLine($"{s.Name}: {s.Score}");
}
// Skip - رد کردن تعدادی از ابتدا
var skipFirst2 = students.OrderBy(s => s.Score).Skip(2);
Console.WriteLine("\n=== All except lowest 2 ===");
foreach (var s in skipFirst2)
{
Console.WriteLine($"{s.Name}: {s.Score}");
}
// صفحهبندی (Pagination)
int pageSize = 2;
int pageNumber = 1;
var page1 = students
.OrderBy(s => s.Name)
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize);
Console.WriteLine($"\n=== Page {pageNumber} (Page Size: {pageSize}) ===");
foreach (var s in page1)
{
Console.WriteLine($"{s.Name}");
}
pageNumber = 2;
var page2 = students
.OrderBy(s => s.Name)
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize);
Console.WriteLine($"\n=== Page {pageNumber} ===");
foreach (var s in page2)
{
Console.WriteLine($"{s.Name}");
}
---
✅ تمرین نهایی درس پانزدهم
برنامهای بنویسید با استفاده از LINQ:
1. لیستی از فروش (Sale) با فیلدهای ProductName، Amount، Date، Category
2. مجموع فروش هر دسته را محاسبه کند
3. 3 محصول پرفروش را پیدا کند
4. میانگین فروش روزانه را محاسبه کند
5. بگوید آیا فروشی بالای 1,000,000 تومان وجود دارد؟
csharp
using System;
using System.Collections.Generic;
using System.Linq;
class Sale
{
public string ProductName { get; set; }
public decimal Amount { get; set; }
public DateTime Date { get; set; }
public string Category { get; set; }
}
class Program
{
static void Main()
{
List<Sale> sales = new List<Sale>
{
new Sale { ProductName = "Laptop", Amount = 15000000, Date = DateTime.Now.AddDays(-5), Category = "Electronics" },
new Sale { ProductName = "Mouse", Amount = 250000, Date = DateTime.Now.AddDays(-4), Category = "Electronics" },
new Sale { ProductName = "Book C#", Amount = 85000, Date = DateTime.Now.AddDays(-3), Category = "Books" },
new Sale { ProductName = "Keyboard", Amount = 850000, Date = DateTime.Now.AddDays(-2), Category = "Electronics" },
new Sale { ProductName = "Pen", Amount = 15000, Date = DateTime.Now.AddDays(-1), Category = "Stationery" },
new Sale { ProductName = "Monitor", Amount = 5500000, Date = DateTime.Now, Category = "Electronics" },
new Sale { ProductName = "Notebook", Amount = 45000, Date = DateTime.Now.AddDays(-3), Category = "Stationery" },
new Sale { ProductName = "Phone", Amount = 12000000, Date = DateTime.Now.AddDays(-1), Category = "Electronics" }
};
Console.WriteLine("=== Sales Report ===\n");
// 1. مجموع فروش هر دسته
var categorySales = sales
.GroupBy(s => s.Category)
.Select(g => new
{
Category = g.Key,
Total = g.Sum(s => s.Amount),
Count = g.Count()
})
.OrderByDescending(g => g.Total);
Console.WriteLine("Sales by Category:");
foreach (var cat in categorySales)
{
Console.WriteLine($" {cat.Category}: {cat.Total:C} ({cat.Count} sales)");
}
// 2. 3 محصول پرفروش
var topProducts = sales
.GroupBy(s => s.ProductName)
.Select(g => new
{
Product = g.Key,
Total = g.Sum(s => s.Amount)
})
.OrderByDescending(p => p.Total)
.Take(3);
Console.WriteLine("\nTop 3 Products:");
int rank = 1;
foreach (var p in topProducts)
{
Console.WriteLine($" {rank}. {p.Product}: {p.Total:C}");
rank++;
}
// 3. میانگین فروش روزانه
var dailySales = sales
.GroupBy(s => s.Date.Date)
.Select(g => new
{
Date = g.Key,
Total = g.Sum(s => s.Amount)
});
double averageDaily = dailySales.Average(d => (double)d.Total);
Console.WriteLine($"\nAverage Daily Sales: {averageDaily:C}");
// 4. آیا فروشی بالای 1,000,000 وجود دارد؟
bool hasHighSale = sales.Any(s => s.Amount > 1000000);
Console.WriteLine($"\nHas any sale above 1,000,000? {hasHighSale}");
// 5. بهترین روز فروش
if (dailySales.Any())
{
var bestDay = dailySales.OrderByDescending(d => d.Total).First();
Console.WriteLine($"\nBest Sales Day: {bestDay.Date.ToShortDateString()} with {bestDay.Total:C}");
}
// 6. محصولاتی که فروش روزانه بالای 500,000 دارند
var highValueProducts = sales
.Where(s => s.Amount > 500000)
.Select(s => s.ProductName)
.Distinct();
Console.WriteLine("\nProducts with sales > 500,000:");
foreach (var p in highValueProducts)
{
Console.WriteLine($" {p}");
}
Console.WriteLine("\nPress Enter to exit...");
Console.ReadLine();
}
}
---
📌 جمعبندی درس پانزدهم
دسته متدها
مرتبسازی OrderBy, OrderByDescending, ThenBy, ThenByDescending
شرطی Any, All, Contains
گروهبندی GroupBy
محاسبات Sum, Average, Count, Min, Max
صفحهبندی Take, Skip
ترکیبهای رایج:
csharp
// زنجیرهای از متدها
var result = collection
.Where(شرط)
.OrderBy(کلید)
.GroupBy(کلید)
.Select(تبدیل)
.Take(تعداد);
`--- 🧪 تمرین برای شما
1. با لیستی از اعداد، میانگین اعداد زوج را محاسبه کنید
2. با لیستی از کلمات، آنها را بر اساس طول مرتب کنید و 3 تای اول را بگیرید
3. با کلاس Product (Name, Price, Stock)، محصولاتی که موجودی آنها کمتر از 5 است را گروهبندی کنید
---
اگر این درس را کامل فهمیدید، بگویید تا قسمت سوم LINQ (Join و عملگرهای پیشرفته) را شروع کنیم. سوالی بود بپرسید.
خیلی خوب، رسیدیم به قسمت سوم LINQ (درس شانزدهم) – Join و عملگرهای پیشرفته.
این قسمت برای ترکیب چندین منبع داده بسیار کاربردی است.
یک پروژه جدید بسازید (dotnet new console) و کدها را همراه من بنویسید و اجرا کنید.
---
📘 درس شانزدهم – LINQ قسمت 3: Join و عملگرهای پیشرفته
🎯 سرفصلها:
1. Join (داخلپیوستن) – معادل INNER JOIN در SQL
2. GroupJoin (گروهپیوستن) – معادل GROUP JOIN
3. SelectMany (flatting) – صاف کردن لیستهای تو در تو
4. متدهای Distinct، Union، Intersect، Except
5. متد Zip – ترکیب دو لیست
6. اجرای دیررس در مقابل اجرای فوری (Deferred vs Immediate)
7. تمرین جامع
---
⏱️ قسمت 1: Join (داخلپیوستن)
Join برای ترکیب دو لیست بر اساس یک کلید مشترک استفاده میشود.
csharp
using System;
using System.Collections.Generic;
using System.Linq;
class Student
{
public int Id { get; set; }
public string Name { get; set; }
public int CourseId { get; set; }
}
class Course
{
public int Id { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
}
class Program
{
static void Main()
{
List<Student> students = new List<Student>
{
new Student { Id = 1, Name = "Ali", CourseId = 101 },
new Student { Id = 2, Name = "Sara", CourseId = 102 },
new Student { Id = 3, Name = "Reza", CourseId = 101 },
new Student { Id = 4, Name = "Neda", CourseId = 103 },
new Student { Id = 5, Name = "Hossein", CourseId = 102 }
};
List<Course> courses = new List<Course>
{
new Course { Id = 101, Title = "C# Programming", Credits = 3 },
new Course { Id = 102, Title = "Web Development", Credits = 4 },
new Course { Id = 103, Title = "Database Design", Credits = 3 }
};
// Method Syntax Join
var studentWithCourse = students.Join(
courses, // لیست دوم
student => student.CourseId, // کلید از لیست اول
course => course.Id, // کلید از لیست دوم
(student, course) => new // نتیجه
{
StudentName = student.Name,
CourseTitle = course.Title,
Credits = course.Credits
}
);
Console.WriteLine("=== Students with Courses (Method Syntax) ===");
foreach (var item in studentWithCourse)
{
Console.WriteLine($"{item.StudentName} is taking {item.CourseTitle} ({item.Credits} credits)");
}
// Query Syntax Join (شبیه SQL)
var querySyntax = from student in students
join course in courses
on student.CourseId equals course.Id
select new
{
StudentName = student.Name,
CourseTitle = course.Title,
Credits = course.Credits
};
Console.WriteLine("\n=== Query Syntax Join ===");
foreach (var item in querySyntax)
{
Console.WriteLine($"{item.StudentName} -> {item.CourseTitle}");
}
}
}
---
⏱️ قسمت 2: GroupJoin (گروهپیوستن)
GroupJoin هر عنصر از لیست اول را با گروهی از عناصر لیست دوم که کلید مشترک دارند، ترکیب میکند.
`csharp class Department { public int Id { get; set; } public string Name { get; set; } } class Employee { public string Name { get; set; } public int DepartmentId { get; set; } public double Salary { get; set; } } // دادهها List<Department> departments = new List<Department> { new Department { Id = 1, Name = "IT" }, new Department { Id = 2, Name = "HR" }, new Department { Id = 3, Name = "Sales" } }; List<Employee> employees = new List<Employee> { new Employee { Name = "Ali", DepartmentId = 1, Salary = 5000000 },
new Employee { Name = "Sara", DepartmentId = 1, Salary = 5500000 },
new Employee { Name = "Reza", DepartmentId = 2, Salary = 4500000 },
new Employee { Name = "Neda", DepartmentId = 1, Salary = 6000000 },
new Employee { Name = "Hossein", DepartmentId = 3, Salary = 4800000 }
};
// GroupJoin - هر دپارتمان با لیست کارمندانش
var departmentsWithEmployees = departments.GroupJoin(
employees,
dept => dept.Id,
emp => emp.DepartmentId,
(dept, empList) => new
{
DepartmentName = dept.Name,
Employees = empList,
EmployeeCount = empList.Count(),
TotalSalary = empList.Sum(e => e.Salary)
}
);
Console.WriteLine("=== Departments with Employees (GroupJoin) ===");
foreach (var dept in departmentsWithEmployees)
{
Console.WriteLine($"\nDepartment: {dept.DepartmentName}");
Console.WriteLine($" Total Employees: {dept.EmployeeCount}");
Console.WriteLine($" Total Salary: {dept.TotalSalary:C}");
Console.WriteLine(" Employees:");
foreach (var emp in dept.Employees)
{
Console.WriteLine($" - {emp.Name}: {emp.Salary:C}");
}
}
// Query Syntax GroupJoin
var queryGroupJoin = from dept in departments
join emp in employees
on dept.Id equals emp.DepartmentId
into empGroup
select new
{
DepartmentName = dept.Name,
Employees = empGroup,
AverageSalary = empGroup.Average(e => e.Salary)
};
Console.WriteLine("\n=== Average Salary by Department ===");
foreach (var dept in queryGroupJoin)
{
Console.WriteLine($"{dept.DepartmentName}: Average Salary = {dept.AverageSalary:C}");
}
---
⏱️ قسمت 3: SelectMany (صاف کردن لیستهای تو در تو)
SelectMany یک لیست از لیستها را به یک لیست تخت تبدیل میکند.
csharp
// مثال 1: لیست کلاسها با لیست دانشجویان
class ClassRoom
{
public string ClassName { get; set; }
public List<string> Students { get; set; }
}
List<ClassRoom> schools = new List<ClassRoom>
{
new ClassRoom
{
ClassName = "Class A",
Students = new List<string> { "Ali", "Reza", "Sara" }
},
new ClassRoom
{
ClassName = "Class B",
Students = new List<string> { "Neda", "Hossein" }
},
new ClassRoom
{
ClassName = "Class C",
Students = new List<string> { "Zahra", "Mohammad", "Fatemeh" }
}
};
// بدون SelectMany (دست و پاگیر)
Console.WriteLine("=== Without SelectMany ===");
foreach (var classroom in schools)
{
foreach (var student in classroom.Students)
{
Console.WriteLine($"{classroom.ClassName} - {student}");
}
}
// با SelectMany (ساده و زیبا)
var allStudents = schools.SelectMany(s => s.Students);
Console.WriteLine("\n=== All Students (Flat List) ===");
Console.WriteLine(string.Join(", ", allStudents));
// با SelectMany و همراه با اطلاعات کلاس
var studentsWithClass = schools.SelectMany(
classroom => classroom.Students,
(classroom, student) => new { Classroom = classroom.ClassName, Student = student }
);
Console.WriteLine("\n=== Students with Class Info ===");
foreach (var item in studentsWithClass)
{
Console.WriteLine($"{item.Student} is in {item.Classroom}");
}
مثال دیگر: اعداد درون لیست
csharp
List<int[]> numberGroups = new List<int[]>
{
new int[] { 1, 2, 3 },
new int[] { 4, 5 },
new int[] { 6, 7, 8, 9 }
};
var flatNumbers = numberGroups.SelectMany(g => g);
Console.WriteLine($"\nFlat numbers: {string.Join(", ", flatNumbers)}"); // 1,2,3,4,5,6,7,8,9
---
⏱️ قسمت 4: Distinct, Union, Intersect, Except
این متدها برای کار با مجموعهها (Set Operations) استفاده میشوند.
csharp
List<int> listA = new List<int> { 1, 2, 3, 4, 5, 5, 6 };
List<int> listB = new List<int> { 4, 5, 6, 7, 8, 9 };
// Distinct - حذف تکراریها
var uniqueA = listA.Distinct();
Console.WriteLine($"Distinct A: {string.Join(", ", uniqueA)}"); // 1,2,3,4,5,6
// Union - اجتماع (بدون تکرار)
var union = listA.Union(listB);
Console.WriteLine($"Union: {string.Join(", ", union)}"); // 1,2,3,4,5,6,7,8,9
// Intersect - اشتراک
var intersect = listA.Intersect(listB);
Console.WriteLine($"Intersect: {string.Join(", ", intersect)}"); // 4,5,6
// Except - تفاضل (موجود در A ولی نه در B)
var except = listA.Except(listB);
Console.WriteLine($"Except (A - B): {string.Join(", ", except)}"); // 1,2,3
مثال با اشیاء (استفاده از IEqualityComparer):
csharp
class Product
{
public int Id { get; set; }
public string Name { get; set; }
}
List<Product> products1 = new List<Product>
{
new Product { Id = 1, Name = "Laptop" },
new Product { Id = 2, Name = "Mouse" },
new Product { Id = 3, Name = "Keyboard" }
};
List<Product> products2 = new List<Product>
{
new Product { Id = 2, Name = "Mouse" },
new Product { Id = 3, Name = "Keyboard" },
new Product { Id = 4, Name = "Monitor" }
};
// Intersect با اشیاء (نیاز به مقایسهکننده)
var commonProducts = products1.Intersect(products2, new ProductComparer());
Console.WriteLine("\n=== Common Products ===");
foreach (var p in commonProducts)
{
Console.WriteLine(p.Name);
}
class ProductComparer : IEqualityComparer<Product>
{
public bool Equals(Product x, Product y)
{
return x.Id == y.Id;
}
public int GetHashCode(Product obj)
{
return obj.Id.GetHashCode();
}
}
---
⏱️ قسمت 5: متد Zip (ترکیب دو لیست)
Zip دو لیست را عنصر به عنصر با هم ترکیب میکند.
csharp
List<string> names = new List<string> { "Ali", "Sara", "Reza", "Neda" };
List<int> ages = new List<int> { 25, 30, 22, 28 };
List<string> cities = new List<string> { "Tehran", "Shiraz", "Isfahan" };
// Zip دو تایی
var nameAge = names.Zip(ages, (name, age) => $"{name} is {age} years old");
Console.WriteLine("=== Zip (Name + Age) ===");
foreach (var item in nameAge)
{
Console.WriteLine(item);
}
// Zip سه تایی (با استفاده از Zip تو در تو)
var nameAgeCity = names
.Zip(ages, (name, age) => new { name, age })
.Zip(cities, (combined, city) => $"{combined.name} is {combined.age} years old from {city}");
Console.WriteLine("\n=== Zip (Name + Age + City) ===");
foreach (var item in nameAgeCity)
{
Console.WriteLine(item);
}
---
⏱️ قسمت 6: اجرای دیررس (Deferred) در مقابل اجرای فوری (Immediate)
csharp
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
// اجرای دیررس (Deferred) - تا زمانی که به نتیجه نیاز نباشد، اجرا نمیشود
var deferredQuery = numbers.Where(n =>
{
Console.WriteLine($"Checking {n}");
return n > 2;
});
Console.WriteLine("Query defined, but not executed yet...");
Console.WriteLine("Now executing with ToList():");
var resultList = deferredQuery.ToList(); // اینجا اجرا میشود
// اجرای فوری (Immediate) - بلافاصله اجرا میشود
Console.WriteLine("\nImmediate execution:");
var immediateResult = numbers
.Where(n => n > 2)
.ToList(); // بلافاصله اجرا میشود
متدهایی که اجرای فوری دارند:
· ToList(), ToArray(), ToDictionary(), ToLookup()
· Count(), Sum(), Average(), Min(), Max()
· First(), FirstOrDefault(), Single(), SingleOrDefault()
csharp
List<int> data = new List<int> { 10, 20, 30, 40, 50 };
// اجرای دیررس
var deferred = data.Where(x => x > 25);
data.Add(60); // اضافه کردن بعد از تعریف query
Console.WriteLine($"Deferred: {string.Join(", ", deferred)}"); // 30,40,50,60
// اجرای فوری
var immediate = data.Where(x => x > 25).ToList();
data.Add(70); // اضافه کردن بعد از ToList
Console.WriteLine($"Immediate: {string.Join(", ", immediate)}"); // 30,40,50,60 (70 اضافه نمیشود)
---
✅ تمرین نهایی درس شانزدهم
برنامهای بنویسید که:
1. دو لیست Customers و Orders بسازد
2. با Join، سفارشهای هر مشتری را نمایش دهد
3. با GroupJoin، مشتریانی که سفارش ندارند را هم نشان دهد
4. با SelectMany، لیست تمام محصولات سفارش داده شده را به صورت تخت نمایش دهد
5. با Union و Intersect، محصولات مشترک بین دو ماه را پیدا کند
csharp
using System;
using System.Collections.Generic;
using System.Linq;
class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public string City { get; set; }
}
class Order
{
public int Id { get; set; }
public int CustomerId { get; set; }
public DateTime Date { get; set; }
public decimal Amount { get; set; }
public List<string> Products { get; set; }
}
class Program
{
static void Main()
{
// دادهها
List<Customer> customers = new List<Customer>
{
new Customer { Id = 1, Name = "Ali Mohammadi", City = "Tehran" },
new Customer { Id = 2, Name = "Sara Ahmadi", City = "Shiraz" },
new Customer { Id = 3, Name = "Reza Karimi", City = "Tehran" },
new Customer { Id = 4, Name = "Neda Hosseini", City = "Isfahan" }
};
List<Order> orders = new List<Order>
{
new Order { Id = 101, CustomerId = 1, Date = DateTime.Now.AddDays(-10), Amount = 1500000,
Products = new List<string> { "Laptop", "Mouse" } },
new Order { Id = 102, CustomerId = 1, Date = DateTime.Now.AddDays(-5), Amount = 250000,
Products = new List<string> { "Keyboard" } },
new Order { Id = 103, CustomerId = 2, Date = DateTime.Now.AddDays(-3), Amount = 85000,
Products = new List<string> { "Book" } },
new Order { Id = 104, CustomerId = 3, Date = DateTime.Now.AddDays(-1), Amount = 5500000,
Products = new List<string> { "Monitor", "Mouse", "Keyboard" } }
};
// 1. Join - سفارشهای هر مشتری
var customerOrders = customers.Join(
orders,
c => c.Id,
o => o.CustomerId,
(c, o) => new { CustomerName = c.Name, OrderId = o.Id, o.Amount, o.Date }
).OrderBy(x => x.CustomerName);
Console.WriteLine("=== Customer Orders (Join) ===");
foreach (var item in customerOrders)
{
Console.WriteLine($"{item.CustomerName}: Order #{item.OrderId} - {item.Amount:C} on {item.Date.ToShortDateString()}");
}
// 2. GroupJoin - مشتریان با لیست سفارشهایشان (حتی بدون سفارش)
var customersWithOrders = customers.GroupJoin(
orders,
c => c.Id,
o => o.CustomerId,
(c, orderList) => new
{
CustomerName = c.Name,
City = c.City,
Orders = orderList,
TotalSpent = orderList.Sum(o => o.Amount),
OrderCount = orderList.Count()
}
);
Console.WriteLine("\n=== Customers with Orders (GroupJoin) ===");
foreach (var c in customersWithOrders)
{
Console.WriteLine($"{c.CustomerName} ({c.City}): {c.OrderCount} orders, Total: {c.TotalSpent:C}");
foreach (var order in c.Orders)
{
Console.WriteLine($" - Order #{order.Id}: {order.Amount:C}");
}
}
// 3. SelectMany - لیست تمام محصولات سفارش داده شده
var allProducts = orders.SelectMany(o => o.Products).Distinct();
Console.WriteLine($"\n=== All Products Ordered ===");
Console.WriteLine(string.Join(", ", allProducts));
// 4. محصولات سفارش داده شده در دو ماه مختلف (مثال با فرض دو دسته سفارش)
var lastWeekProducts = orders
.Where(o => o.Date >= DateTime.Now.AddDays(-7))
.SelectMany(o => o.Products)
.Distinct();
var olderProducts = orders
.Where(o => o.Date < DateTime.Now.AddDays(-7))
.SelectMany(o => o.Products)
.Distinct();
Console.WriteLine("\n=== Products in Last Week ===");
Console.WriteLine(string.Join(", ", lastWeekProducts));
Console.WriteLine("\n=== Products Older than Week ===");
Console.WriteLine(string.Join(", ", olderProducts));
// 5. Union و Intersect
var allUniqueProducts = lastWeekProducts.Union(olderProducts);
var commonProducts = lastWeekProducts.Intersect(olderProducts);
Console.WriteLine($"\n=== All Unique Products: {string.Join(", ", allUniqueProducts)}");
Console.WriteLine($"=== Common Products: {string.Join(", ", commonProducts)}");
// 6. مشتریانی که بیش از یک سفارش دارند
var repeatCustomers = customersWithOrders.Where(c => c.OrderCount > 1);
Console.WriteLine("\n=== Customers with Multiple Orders ===");
foreach (var c in repeatCustomers)
{
Console.WriteLine($"{c.CustomerName}: {c.OrderCount} orders");
}
Console.WriteLine("\nPress Enter to exit...");
Console.ReadLine();
}
}
`--- 📌 جمعبندی درس شانزدهم دسته متدها کاربرد Join Join, GroupJoin ترکیب دو لیست صاف کردن SelectMany تبدیل لیست تو در تو به تخت مجموعه Distinct, Union, Intersect, Except عملیات مجموعهای ترکیب Zip ترکیب عنصر به عنصر اجرا Deferred, Immediate زمان اجرای query نکات مهم: · Join = INNER JOIN در SQL · GroupJoin = LEFT JOIN + GROUP BY · SelectMany = فلَت کردن لیستها · بیشتر متدهای LINQ اجرای دیررس دارند · متدهای ToList, Count, Sum اجرای فوری دارند --- 🧪 تمرین برای شما 1. دو لیست از دانشجویان و نمرات بسازید و با Join ترکیب کنید 2. لیستی از سفارشات با آیتمهایشان بسازید و با SelectMany همه آیتمها را لیست کنید 3. با Union و Intersect، اعداد مشترک بین دو آرایه را پیدا کنید --- 🎉 تبریک! شما LINQ را کامل یاد گرفتید! درسهای LINQ تمام شد: درس عنوان درس 14 LINQ قسمت 1 – متدهای پایه (Where, Select, First) درس 15 LINQ قسمت 2 – مرتبسازی، گروهبندی، محاسبات درس 16 LINQ قسمت 3 – Join و عملگرهای پیشرفته --- اگر این درس را کامل فهمیدید، میتوانیم درس بعدی را شروع کنیم. مباحث پیشنهادی برای ادامه: · درس 17: کار با فایلها (File I/O) · درس 18: Async/Await (برنامهنویسی نامن synchronous) · درس 19: Attribute و Reflection · درس 20: Entity Framework (کار با دیتابیس) بگویید کدام مبحث را میخواهید. سوالی بود بپرسید.