LinQ Performance - "multiple enumerations" khi sử dụng IEnumerable

Trong các dự án thực tế, khi viết các phương thức dùng chung cho các yêu cầu khác nhau, với các phương thức liên quan đến tập hợp (array, collection, list), các lập trình viên thường sử dụng IEnumerable<T>. Phương thức này cơ bản có dạng như sau:
 

public IEnumerable<T> GetTObjects()
{
    //do something
    //IEnumerable<T> result = ....;
    //return result;
}

Sau đó, ở các phương thức khác chỉ cần gọi phương thức GetTObjects() và tiếp tục xử lý tập hợp. Với tập hợp này, do yêu cầu nghiệp vụ, chúng ta phải xử lý 1 chuỗi câu lệnh như kiểm tra tập hợp rỗng, tìm các phần tử thỏa mãn 1 điều kiện nào đó, loại bỏ bớt phần tử theo điều kiện nào đó... Và trong rất nhiều trường hợp như thế này, chúng ta thường hay gặp cách viết mã chương trình theo dạng như sau:

 

public void ProcessTObjects(){
  
  var objects = GetTObjects();

  if(!objects.Any()){
     //do something
  }

  var object = objects.FirstOrDefault(Func<T, bool> samplePredicate);

  if(object != null) {
     //do something
  }

  var otherObjects = objects.Where(Func<T, bool> samplePredicate);
  
  foreach(var obj in otherObjects){
     
     //do something 
  }
  
  //more code ...
}

Cách viết này theo nghiệp vụ là ok và đúng logic rồi. Tuy nhiên, dạng code theo cách như trên sẽ gây ra 'Multiple Enumerations', hiểu 1 cách đơn giản là IEnumerable<T> được evaluate lại nhiều lần và như vậy sẽ làm giảm performance của chương trình. Trong đoạn mã dạng trên, sẽ có ít nhất 3 lần IEnumerable<T> được evaluate:

- Lần 1 là dòng code sử dụng phương thức Any().
- Lần 2 là dòng code sử dụng phương thức FirstOrDefault().
- Lần 3 là dòng code sử dụng phương thức Where().

Để tránh điều này xảy ra, chúng ta phải làm cho IEnumerable<T> được evaluate 1 lần duy nhất trong phương thức trên. Để thực hiện điều này, chúng ta nên cast IEnumerable<T> sang dạng List<T> hoặc Array<T> ngay tại thời điểm lấy về IEnumerable<T>. Cụ thể ở đây, chúng ta sửa như sau:
 

Chuyển: 'var objects = GetTObjects()' thành 'var objects = GetTObjects().ToList()' hoặc  'var objects = GetTObjects().ToArray()'


Với cách xử lý này, ở các đoạn code tiếp theo, chúng ta sẽ làm việc với List<T> hoặc Array<T>, chứ không còn phải làm việc với IEnumerable<T> nữa, và đảm bảo được IEnumerable<T> chỉ được evaluate 1 lần duy nhất.

Các bạn có thể chuyển đoạn code sang 1 chương trình cụ thể và test thử để xem hiệu quả. Ngoài ra, để nắm vững các kiến thức về LINQ cũng như tránh được các lỗi trong LINQ theo dạng code như trong bài viết đã đề cập, các bạn nên tìm hiểu Deffered Execution và Immediate Execution trong linQ hoặc có thể tham dự khóa học về LINQ của trang tedu.


Tác giả: Nguyễn Thanh Sơn

Chú ý: Tất cả các bài viết trên TEDU.COM.VN đều thuộc bản quyền TEDU, yêu cầu dẫn nguồn khi trích lại trên website khác.

Lên trên