CodeGym /Các khóa học /C# SELF /Phép chiếu dữ liệu với Sel...

Phép chiếu dữ liệu với Select

C# SELF
Mức độ , Bài học
Có sẵn

1. Giới thiệu

Khi xem phim bộ, bạn đâu phải lúc nào cũng biết hết thông tin về tất cả nhân vật, chỉ biết cái gì cần cho cốt truyện thôi. Lập trình cũng vậy: nhiều lúc mình không cần cả object, chỉ cần vài trường, giá trị hoặc thậm chí là một số phép tính dựa trên chúng.

Phép chiếu trong LINQ là chuyển đổi từng phần tử của sequence gốc sang một dạng mới. Thường dùng method Select. Bạn cứ tưởng tượng Select như một cái máy thần kỳ, nó lấy từng phần tử, áp dụng function rồi trả về kết quả. Vậy thôi, không có gì ảo diệu cả.

Khi nào cần phép chiếu?

  • Muốn in ra chỉ tên user, không cần hết info về họ.
  • Chuẩn bị email marketing: từ object khách hàng chỉ lấy địa chỉ và tên.
  • Tính toán: cộng thuế vào giá sản phẩm và tạo collection mới.
  • Cho UI: chỉ hiển thị một phần dữ liệu, không làm nặng giao diện.

2. Method Select: cú pháp và cách dùng cơ bản

Cú pháp cơ bản

LINQ hỗ trợ hai style: Method SyntaxQuery Syntax. Với Select thì kết quả như nhau ở cả hai style.

Method-syntax

var query = myCollection.Select(x => x.CaiCanTraVe);

Ở đây x là biến đại diện cho từng phần tử trong collection, bên phải là expression biến phần tử thành cái bạn muốn.

Query-syntax

var query = from x in myCollection
            select x.CaiCanTraVe;

Nhìn giống SQL hơn chút.

Ví dụ đơn giản: danh sách số

var numbers = new List<int> { 1, 2, 3, 4, 5 };
// Muốn lấy bình phương của chúng
var squares = numbers.Select(n => n * n);

foreach (var s in squares)
{
    Console.WriteLine(s); // 1, 4, 9, 16, 25
}

Vậy là mình đã biến collection số thành collection bình phương của chúng — cực nhanh, không cần for thủ công!

Ví dụ 2

Ở ví dụ trước mình có class Product:

public class Product
{
    public string Name { get; set; }
    public double Price { get; set; }
}

Giả sử có list như này:

var products = new List<Product>
{
    new Product { Name = "Socola", Price = 120 },
    new Product { Name = "Pho mai", Price = 250 },
    new Product { Name = "Banh mi", Price = 55 }
};

Nếu chỉ muốn lấy tên sản phẩm thôi thì đâu cần cả object đúng không? Chỉ cần list tên:

var names = products.Select(p => p.Name);
foreach (var name in names)
{
    Console.WriteLine(name); // Socola, Pho mai, Banh mi
}

3. Collection trả về — có những kiểu nào?

Lưu ý là Select luôn trả về collection (chính xác là IEnumerable<TOutput>), trong đó TOutput là kiểu kết quả của function. Bạn tự quyết định nó là string, số, anonymous type hay object mới luôn.

Phép chiếu sang kiểu mới

Ví dụ, bạn muốn lấy không phải object gốc mà là "phép chiếu" của nó:

var projections = products.Select(p => new { p.Name, GiaCoVAT = p.Price * 1.2 });

foreach (var item in projections)
{
    Console.WriteLine($"{item.Name}: {item.GiaCoVAT}");
}

Ở đây mình tạo collection mới gồm các anonymous object — có tên và giá đã cộng VAT tưởng tượng!

4. Trả về class mới hoặc anonymous type

Bạn có thể chọn: tạo class riêng cho kết quả hoặc dùng anonymous type nếu chỉ cần cấu trúc đó "tạm thời".

Anonymous type

var result = products.Select(p => new { Ten = p.Name, GiaCu = p.Price, GiaMoi = p.Price + 40 });
foreach (var p in result)
{
    Console.WriteLine($"{p.Ten}: Truoc la {p.GiaCu}, gio la {p.GiaMoi}");
}

Anonymous type rất tiện để tạo kết quả nhanh, nhất là khi không cần tạo class chỉ cho một thao tác.

Dùng class riêng cho phép chiếu

Tạo class mới:

public class ProductDto
{
    public string Name { get; set; }
    public double PriceWithTax { get; set; }
}

Bây giờ trong LINQ query:

var productsWithTax = products.Select(p => new ProductDto
{
    Name = p.Name,
    PriceWithTax = p.Price * 1.2
});

foreach (var p in productsWithTax)
{
    Console.WriteLine($"{p.Name}: {p.PriceWithTax}");
}

5. Truy cập collection và property lồng nhau

Nếu trong object có collection thì sao? Ví dụ mỗi user có list đơn hàng.

public class User
{
    public string Name { get; set; }
    public List<Product> Purchases { get; set; }
}

Bạn muốn lấy list tất cả các list mua hàng? Không vấn đề!

var allPurchases = users.Select(u => u.Purchases);

foreach (var purchaseList in allPurchases)
{
    foreach (var product in purchaseList)
    {
        Console.WriteLine(product.Name);
    }
}

Lưu ý! Lúc này bạn có collection các collection, không phải list phẳng các sản phẩm. Làm sao để lấy MỘT list chứa tất cả sản phẩm của mọi user? Đó là chủ đề bài sau — mình sẽ nói về SelectMany.

6. Một số lưu ý hay

Chuyển sang kiểu collection khác: ToList() và các bạn

Select trả về IEnumerable<T>. Nếu muốn list thường (List<T>), chỉ cần thêm .ToList():

var nameList = products.Select(p => p.Name).ToList();

Giờ bạn có list chuẩn, dùng như bình thường.

Dùng index của phần tử

Đôi khi cần biết phần tử đang xử lý là thứ mấy trong collection. Method Select có overload này:

var indexed = products.Select((product, index) => new { Index = index, Name = product.Name });
foreach (var item in indexed)
{
    Console.WriteLine($"{item.Index}: {item.Name}");
}

Cách này dùng để chuẩn bị dữ liệu cho list đánh số chẳng hạn.

Phép chiếu trong lập trình

Thành thạo Select là câu hỏi phổ biến khi phỏng vấn. Không có nó thì khó mà code app hiện đại:

  • Lấy ra chỉ dữ liệu cần thiết từ DB hoặc service ngoài.
  • Chuẩn bị dữ liệu để truyền giữa các layer của app.
  • Chuyển đổi dữ liệu sang format phù hợp để hiển thị hoặc gửi qua mạng.
  • Tiết kiệm RAM (không kéo cả object, chỉ lấy trường cần).

Các công ty làm với dữ liệu lớn thường xây dựng business logic chính dựa trên cái này.

7. Lỗi phổ biến và đặc điểm

Nhiều bạn mới học hay gặp bug khó chịu — thường do chưa hiểu thực thi lười (deferred execution) trong LINQ. Ví dụ, sau khi gọi Select thì query chưa chạy ngay — chỉ khi bạn thực sự duyệt collection (foreach, ToList(), v.v.). Nhờ vậy bạn có thể xâu chuỗi xử lý, nhưng đôi khi kết quả sẽ thay đổi nếu dữ liệu gốc bị đổi trước khi dùng kết quả query.

Cũng nhớ là Select không thay đổi collection gốc — nó tạo collection mới. Nên nếu bạn mong chờ thấy trường mới trong products sau products.Select(...) thì sẽ không có đâu.

Xử lý null: nếu phép chiếu có thể truy cập trường null, nhớ kiểm tra nhé. Từ C# 8+ sẽ có cảnh báo compile nếu có nguy cơ dính NullReferenceException.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION