So sánh cốt lõi
| Đặc điểm | Select | SelectMany |
| Kết quả mỗi element | 1 kết quả (1:1 mapping) | 0..N kết quả (1:N mapping) |
| Kiểu trả về | IEnumerable<TResult> | IEnumerable<TResult> (flattened) |
| Input selector | Func<T, TResult> | Func<T, IEnumerable<TResult>> |
| Hành vi | Projection (chiếu) | Projection + Flatten (làm phẳng) |
Ví dụ minh họa trực quan
class Category
{
public string Name { get; set; }
public List Products { get; set; }
public static List GetCategories()
{
return new List
{
new Category { Name = "Electronics", Products = new() { "Phone", "Laptop", "Tablet" } },
new Category { Name = "Clothing", Products = new() { "Shirt", "Pants" } },
new Category { Name = "Books", Products = new() { "C# Guide", "LINQ Book" } }
};
}
}
Select - Trả về IEnumerable<List<string>> (Collection of Collections)
var result = Category.GetCategories()
.Select(c => c.Products); // Mỗi category → 1 List
// Kết quả: IEnumerable>
// [
// ["Phone", "Laptop", "Tablet"],
// ["Shirt", "Pants"],
// ["C# Guide", "LINQ Book"]
// ]
foreach (var productList in result)
{
foreach (var product in productList)
{
Console.WriteLine(product);
}
}
SelectMany - Trả về IEnumerable<string> (Flattened)
var result = Category.GetCategories()
.SelectMany(c => c.Products); // Mỗi category → nhiều product → gộp thành 1 list
// Kết quả: IEnumerable (phẳng)
// ["Phone", "Laptop", "Tablet", "Shirt", "Pants", "C# Guide", "LINQ Book"]
foreach (var product in result)
{
Console.WriteLine(product);
}
Overloads của SelectMany
1. Basic: Func<T, IEnumerable<TResult>>
var allProducts = categories.SelectMany(c => c.Products);
2. With Index: Func<T, int, IEnumerable<TResult>>
var withIndex = categories.SelectMany((c, index) => c.Products.Select(p => $"{index}: {p}"));
3. Result Selector (Projection cuối): Func<T, TCollection, TResult>
// Kết hợp category name với product
var result = categories.SelectMany(
c => c.Products, // Collection selector
(category, product) => new { Category = category.Name, Product = product } // Result selector
);
// Kết quả: IEnumerable { Category, Product }
Use Cases thực tế
1. Flatten nested collections
// Tất cả OrderDetails từ tất cả Orders
var allDetails = orders.SelectMany(o => o.OrderDetails);
2. Cross Join (Cartesian Product)
var sizes = new[] { "S", "M", "L" };
var colors = new[] { "Red", "Blue" };
var combinations = sizes.SelectMany(size => colors.Select(color => $"{size}-{color}"));
// S-Red, S-Blue, M-Red, M-Blue, L-Red, L-Blue
3. Hierarchical Data Traversal
class Employee
{
public string Name { get; set; }
public List Subordinates { get; set; }
}
// Lấy tất cả nhân viên trong cây (flatten hierarchy)
IEnumerable GetAllEmployees(Employee root)
{
return new[] { root }.Concat(root.Subordinates.SelectMany(GetAllEmployees));
}
4. Query Syntax
// SelectMany trong query syntax = từ khóa "from" thứ 2 trở đi
var query = from category in categories
from product in category.Products
select new { category.Name, product };
// Tương đương SelectMany
Performance Notes
- Cả hai đều deferred execution (lười).
SelectMany tránh tạo intermediate collections so với Select + Concat.
- Với collection lớn: cân nhắc
AsParallel().SelectMany() (PLINQ).
SelectMany vs Select + SelectMany
// Cách cũ (trước SelectMany)
var oldWay = categories.Select(c => c.Products).SelectMany(p => p);
// Cách hiện đại
var newWay = categories.SelectMany(c => c.Products);