Series C# hay ho: LINQ – Lột mặt nạ sự “bá đạo” của LINQ

1. LINQ có thực sự dễ như ta tưởng?

LINQ không phải là 1 khái niệm xa lạ đối với các C#.NET developer, nhất là những bạn hay làm việc với database (LINQ to SQL). Tuy nhiên, đa phần trong chúng ta đều sử dụng LINQ mà không biết rõ nó hoạt động như thế nào. Chắc ai cũng từng viết những dòng code như sau:

 public class Student
 {
    public string Name { get; set; }
    public int Age { get; set; }
 }
var students = new List<Student>();
var result = students.Where(stu => stu.Age < 20);

Dòng code rất dễ hiểu, lọc ra những học sinh có số tuổi < 20. Tuy nhiên, liệu bạn có trả lời được những câu hỏi sau:

  1. Dấu “=>” là dấu gì. Toàn bộ cụm “stu => stu.Age < 20” được gọi là gì?
  2. Hàm Where được viết như thế nào, nhận parameter gì vào, trả ra giá trị gì?
  3. Tại sao IDE tại biết stu là 1 Student để có thể nhắc lệnh?
  4. Có thể viết 1 thứ tương tự như LINQ trong javascript không?

Nếu bạn chỉ trả lời đc 1,2 hoặc không trả lời đc câu hỏi nào, bạn chưa thực sự hiểu LINQ. Đừng lo, ngày xưa mình cũng thế. Bài viết này sẽ giúp bạn giải đáp những câu hỏi trên, cũng như hiểu rõ hơn về LINQ. (Đáp án ở cuối bài viết).

2. Ôn lại kiến thức

LINQ được implement bằng cách tổng hợp khá nhiều khái niệm hay ho và phức tạp của ngôn ngữ C#. Nếu bạn nào thường xuyên theo dõi blog, đã đọc những bài viết trước thì chắc là biết rồi. Với các bạn chưa biết, những khái niệm đó bao gồm:

pr-2

3. Cùng “lột mặt nạ” LINQ

Để sử dụng LINQ, chúng ta phải dùng namespace System.Linq. Thật ra Linq là 1 danh sách các extension method, thêm một số method cho interface IENumerable. Vì các class List, DbSet, … đều implement interface này, do đó chúng đều có thể gọi Linq method.

Đây là signature của 3 Linq method cơ bản: Where, Select và First.

 public static class Enumerable
 {
   public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
   public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector);
   public static TSource First<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
}

Có bạn sẽ ngạc nhiên: Cái đống xấu xí gớm ghiếc khó hiểu này chính là Linq xinh đẹp của mình sao? Vâng, ngày xưa sau khi xem bạn gái tẩy trang xong mình cũng có cảm giác như các bạn vậy =)))

Trong phạm vi bài viết, mình chỉ giải thích method Where, các bạn có thể dựa vào đó để tự tìm hiểu thêm. Ta thấy method nhận vào 1 object có kiểu Func<TSource, bool> (link), có a thấy quen ko? Đây là 1 delegate (con trỏ hàm), trỏ tới 1 method có params là TSource, kiểu trả về là bool. TSource ở đây là kiểu dữ liệu generic, do đó ta có thể khai báo kiểu gì cũng được.

Nếu bạn chưa rành lắm về generic, hãy xem kiểu TSource như kiểu Student, signature của hàm Where sẽ thành:

 public static IEnumerable<Student> Where<Student>(this IEnumerable<Student> source, Func<Student, bool> predicate);

Mình sẽ viết lại method Linq ở đầu bài, từ phức tạp đến đơn giản. Mình không dùng var để các bạn hiểu rõ kiểu dữ liệu đưa vào và trả về:

//Gán method vào delegate
public bool FindStudent(Student stu) { return stu.Age &amp;amp;lt; 20; }

//Sử dụng lambda expression sẽ ngắn hơn
//Xem lại bài viết về lamda expression
Func<Student, bool> func1 = FindStudent;
func1 = stu => stu.Age < 20;

List<Student> students = new List<Student>();
IEnumerable<Student> result = students.Where<Student>(func1);

//Với generic trong C#, ta không cần nói rõ kiểu dữ liệu.
//Dựa vào kiểu dữ liệu của Func&amp;amp;lt;Student, bool&amp;amp;gt; truyền vào
//C# tự hiểu ta dùng hàm Where với kiểu dữ liệu Student

//Ta bỏ kiểu Student, thay func bằng lambda
var result = students.Where(stu => stu.Age < 20);
 

4. Tự viết LINQ

Đến đây, chúng ta có thể tự viết 1 Linq method . Mọi chuyện rất đơn giản, bạn chỉ cần viết 1 extension method cho IENumerable, với signature tương tự như Linq. Đây là method Linq do mình viết

 public static class MyLinq
 {
   //Extension method
   public static IEnumerable<TSource> MyWhere<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
   {
     //Loop toàn bộ cái item trong danh sách truyền vào
     foreach (var item in source)
     {
       // Callback lại hàm đã truyền vào, hàm này trả giá trị boolean
       // Nếu hàm callback return true, đưa item đó vào IENumerable kết quả
       // Xem lại vài IEnumerable và yield
       if (predicate(item))  yield return item;
     }
   }
 }
 

Method này khá đơn giản, vì mình đã bỏ qua các bước check null v…v, tuy nhiên nó cho kết quả tương tự như method Where trong Linq.

var result = students.MyWhere(stu => stu.Age < 20);
 

questions-answers22

Tới đây, mình sẽ trả lời 4 câu hỏi đã nêu ra ở đầu bài.

  1. Dấu “=>” là dấu gì. Toàn bộ cụm “stu => stu.Age < 20” được gọi là gì? Dấu “=>” là dấu “go-to”, toàn bộ cụm đi kèm là 1 lambda expression. Bản chất của lambda expression là 1 anonymous method.
  2. Hàm Where được viết như thế nào, nhận parameter gì vào, trả ra giá trị gì? Hàm Where là 1 extension method của class IEnumerable, nhận vào 1 delegate với kiểu trả về là bool, trả ra 1 IEnumerable đã filter.
  3. Tại sao IDE tại biết stu là 1 Student để có thể nhắc lệnh? Ở đây ta sử dụng lambda expression, stu là 1 param của hàm. Do Linq sử dụng generic, list students là 1 list với kiểu dữ liệu Student. do đó C# tự hiểu stu là 1 param với kiểu dữ liệu Student.
  4. Có thể viết 1 thứ tương tự như LINQ trong javascript không? Có thể. Bạn có thể tìm hiểu thêm về underscore và lodash, 2 thư viện hỗ trợ linq cho javascript.

Bài viết kết thúc ở đây. Bạn nào muốn tìm hiểu chuyên sâu hơn có thể tìm đọc C# in Depth. 70% kiến thức mình có được về LINQ và cái khái niệm trên là nhờ đọc cuốn này .Vì LINQ là 1 chủ đề khá phức tạp, cần giải thích nhiều, nếu bạn nào còn thắc mắc có thể để lại comment hoặc gửi mail cho mình nhé.

14 thoughts on “Series C# hay ho: LINQ – Lột mặt nạ sự “bá đạo” của LINQ”

  1. Em muốn hỏi là với ASP.NET MVC mới hiện nay như 5 or Core 1 chẳng hạn thì mình sẽ dùng cả LINQ và entity framework tùy trường hợp ạ? mình sẽ sử dụng 2 cái kết hợp ntn ạ?

    Like

  2. Em đọc qua một số các cuộc thảo luận trước đây thì người ta đánh giá performance của linq là không cao nhưng không thể nào phủ nhận được việc linq giúp dev code rất nhanh và tiện, chẳng hạn mười mấy dòng code gom lại vẻn vẹn 1 dòng :D, không biết hiện tại performance của linq có được cải thiện chưa, Em thì cũng mới bắt đầu học linq thời gian gần đây.

    Like

    1. http://stackoverflow.com/questions/14893924/for-vs-linq-performance-vs-future
      The best practice depends on what you need:

      Development speed and maintainability: LINQ
      Performance (according to profiling tools): manual code
      LINQ really does slow things down with all the indirection. Don’t worry about it as 99% of your code does not impact end user performance.

      Đa phần các trường hợp anh dùng thì linq làm code chậm đi khoảng… vài chục vài trăm milisecond nhưng tiết kiệm được vài phút viết code nên cứ dùng thôi =)))

      Like

  3. Mình thấy Linq viết code nhanh nhưng khả năng dùng lại code rất hạn chế. Ví dụ. Ờ Form Lớp học đã lấy ra danh sách LỚP HỌC. Giờ muốn gọi lại hàm lấy Danh sách LỚP HỌC ở form SINHVIEN thì cực khó, phải viết lại code lấy Danh sách LỚP HỌC 1 lần nữa.

    Like

  4. Có tài liệu tiếng Việt nào học LINQ không ạ? Em đang làm web service SOAP LINQ

    Like

Leave a comment