2013-02-22 22 views
12

Nếu tôi không sai, ToList() phương pháp lặp trên mỗi phần tử của bộ sưu tập được cung cấp và thêm chúng vào thể hiện mới của Danh sách và trở về này instance.Suppose một ví dụToList trong LINQ

//using linq 
list = Students.Where(s => s.Name == "ABC").ToList(); 

//traditional way 
foreach (var student in Students) 
{ 
    if (student.Name == "ABC") 
    list.Add(student); 
} 

Tôi nghĩ cách truyền thống là nhanh hơn, vì nó lặp lại chỉ một lần, nơi như trên của Linq lặp lại hai lần một lần cho phương pháp Where và sau đó cho ToList() phương pháp.

Dự án tôi đang làm bây giờ đã sử dụng rộng rãi Danh sách và tôi thấy có rất nhiều loại sử dụng ToList() và các phương pháp khác có thể được thực hiện tốt hơn như trên nếu tôi lấy danh sách biến như IEnumerable và loại bỏ .ToList() và sử dụng nó thêm như IEnumerable.

Những điều này có ảnh hưởng gì đến hiệu suất không?

+2

Hãy xem 'Where' như một mệnh đề 'if' trong một vòng lặp, không phải là vòng lặp. Trên thực tế, 'Where' sẽ được sử dụng khi' ToList' liệt kê chuỗi. –

Trả lời

9

Những điều này có ảnh hưởng gì đến hiệu suất không?

Điều đó tùy thuộc vào mã của bạn. Hầu hết thời gian, bằng cách sử dụng LINQ không gây ra một hit hiệu suất nhỏ. Trong một số trường hợp, lần truy cập này có thể có ý nghĩa đối với bạn, nhưng bạn chỉ nên tránh LINQ khi bạn biết rằng nó quá chậm đối với bạn (tức là nếu định hình mã của bạn cho thấy LINQ là lý do mã của bạn chậm).

Nhưng bạn có quyền sử dụng ToList() quá thường xuyên có thể gây ra các vấn đề về hiệu suất đáng kể. Bạn chỉ nên gọi số ToList() khi cần. Lưu ý rằng cũng có những trường hợp khi thêm ToList() có thể cải thiện hiệu suất rất nhiều (ví dụ: khi bộ sưu tập được tải từ cơ sở dữ liệu mỗi lần được lặp lại).

Về số lần lặp lại: tùy thuộc vào ý chính xác của bạn là “lặp lại hai lần”. Nếu bạn đếm số lần MoveNext() được gọi trên một số bộ sưu tập, thì có, sử dụng Where() theo cách này dẫn đến lặp lại hai lần. Chuỗi các hoạt động đi như thế này (để đơn giản hóa, tôi sẽ giả định rằng tất cả các mặt hàng phù hợp với điều kiện):

  1. Where() được gọi, không lặp lại cho bây giờ, Where() trả về một đếm đặc biệt.
  2. ToList() được gọi, gọi số MoveNext() trên số điện thoại được trả về từ Where().
  3. Where() hiện gọi số MoveNext() trên bộ sưu tập gốc và nhận giá trị.
  4. Where() gọi vị từ của bạn, trả về true.
  5. MoveNext() được gọi từ ToList() trả về, ToList() nhận giá trị và thêm nó vào danh sách.
  6. ...

Điều này có nghĩa là nếu tất cả n mục trong bộ sưu tập ban đầu phù hợp với điều kiện, MoveNext() sẽ được gọi là 2 n lần, n lần từ Where()n lần từ ToList().

+1

Mô tả hay (miễn là đây là LINQ to Objects). LINQ to SQL/EF sẽ chỉ lặp qua kết quả dữ liệu được. –

+1

@JimWooley Vâng, tôi cho rằng đây là LINQ đối tượng, đó là những gì câu hỏi có vẻ là về (mặc dù nó không nói rõ ràng). – svick

+0

Điều này là không quan trọng nếu LINQ của nó đối tượng hoặc LINQ to SQL/EF. Bạn có thể thấy câu trả lời của tôi nếu bạn quan tâm đến lý do tại sao. MoveNext KHÔNG được gọi là 2n lần. Chỉ có một lần lặp được thực hiện. – Evelie

4
var list = Students.Where(s=>s.Name == "ABC"); 

Điều này sẽ chỉ tạo truy vấn và không lặp lại các phần tử cho đến khi truy vấn được sử dụng. Bằng cách gọi ToList() trước tiên sẽ thực hiện truy vấn và do đó chỉ lặp lại các phần tử của bạn một lần.

List<Student> studentList = new List<Student>(); 
var list = Students.Where(s=>s.Name == "ABC"); 
foreach(Student s in list) 
{ 
    studentList.add(s); 
} 

ví dụ này cũng sẽ chỉ lặp lại một lần. Bởi vì nó chỉ được sử dụng một lần. Hãy ghi nhớ rằng danh sách sẽ lặp lại tất cả học sinh mọi lúc được gọi là .. Không chỉ những người có tên là ABC. Kể từ khi một truy vấn của nó.

Và đối với các cuộc thảo luận sau, Ive đã thực hiện testexample. Có lẽ nó không phải là thực hiện tốt nhất của IEnumable nhưng nó làm những gì nó phải làm.

Đầu tiên chúng tôi có danh sách của chúng tôi

public class TestList<T> : IEnumerable<T> 
{ 
    private TestEnumerator<T> _Enumerator; 

    public TestList() 
    { 
     _Enumerator = new TestEnumerator<T>(); 
    } 

    public IEnumerator<T> GetEnumerator() 
    { 
     return _Enumerator; 
    } 

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
    { 
     throw new NotImplementedException(); 
    } 

    internal void Add(T p) 
    { 
     _Enumerator.Add(p); 
    } 
} 

Và kể từ khi chúng tôi muốn đếm bao nhiêu lần MoveNext được gọi là chúng ta phải thực hiện aswel tùy chỉnh Enumerator của chúng tôi. Quan sát trong MoveNext chúng ta có một bộ đếm tĩnh trong chương trình của chúng ta.

lớp công khai TestEnumerator: IEnumerator { mục công khai FirstItem = null; mục công khai CurrentItem = null;

public TestEnumerator() 
    { 
    } 

    public T Current 
    { 
     get { return CurrentItem.Value; } 
    } 

    public void Dispose() 
    { 

    } 

    object System.Collections.IEnumerator.Current 
    { 
     get { throw new NotImplementedException(); } 
    } 

    public bool MoveNext() 
    { 
     Program.Counter++; 
     if (CurrentItem == null) 
     { 
      CurrentItem = FirstItem; 
      return true; 
     } 
     if (CurrentItem != null && CurrentItem.NextItem != null) 
     { 
      CurrentItem = CurrentItem.NextItem; 
      return true; 
     } 
     return false; 
    } 

    public void Reset() 
    { 
     CurrentItem = null; 
    } 

    internal void Add(T p) 
    { 
     if (FirstItem == null) 
     { 
      FirstItem = new Item<T>(p); 
      return; 
     } 
     Item<T> lastItem = FirstItem; 
     while (lastItem.NextItem != null) 
     { 
      lastItem = lastItem.NextItem; 
     } 
     lastItem.NextItem = new Item<T>(p); 
    } 
} 

Và sau đó chúng ta có một mục tùy chỉnh mà chỉ kết thúc tốt đẹp giá trị của chúng tôi

public class Item<T> 
{ 
    public Item(T item) 
    { 
     Value = item; 
    } 

    public T Value; 

    public Item<T> NextItem; 
} 

Để sử dụng mã thực tế chúng ta tạo ra một "danh sách" với 3 mục.

public static int Counter = 0; 
    static void Main(string[] args) 
    { 
     TestList<int> list = new TestList<int>(); 
     list.Add(1); 
     list.Add(2); 
     list.Add(3); 

     var v = list.Where(c => c == 2).ToList(); //will use movenext 4 times 
     var v = list.Where(c => true).ToList(); //will also use movenext 4 times 


     List<int> tmpList = new List<int>(); //And the loop in OP question 
     foreach(var i in list) 
     { 
      tmpList.Add(i); 
     }         //Also 4 times. 
    } 

Và kết luận? Làm thế nào để nó đạt hiệu suất? MoveNext được gọi là n + 1 lần trong trường hợp này. Bất kể chúng ta có bao nhiêu món đồ. Và cũng là WhereClause không quan trọng, anh ta vẫn sẽ chạy MoveNext 4 lần. Bởi vì chúng tôi luôn chạy truy vấn của mình trong danh sách ban đầu. Hiệu năng duy nhất mà chúng tôi sẽ thực hiện là khung công tác LINQ thực tế và các cuộc gọi của nó. Các vòng thực tế được thực hiện sẽ giống nhau.

Và trước khi bất kỳ ai hỏi tại sao N + 1 lần và không phải N lần. Bởi vì anh ta trả về sai lầm lần cuối khi anh ấy ra khỏi các yếu tố. Làm cho nó số lượng các yếu tố + kết thúc của danh sách.

+1

Nếu bạn so sánh hai mẫu, một với LINQ thực sự lặp lại (tức là gọi 'MoveNext()' cho mỗi phần tử) bộ sưu tập hai lần: một lần trong 'Where()' và một lần trong 'ToList()'. (Mặc dù lần thứ hai, nó có thể là một bộ sưu tập nhỏ hơn.) Điều này thường sẽ không ảnh hưởng đến hiệu suất nhiều, nhưng nó có thể có tác động. – svick

+1

Không vì tôi đã đề cập đến một truy vấn không được thực thi. Nó được thực thi đầu tiên khi bạn thực sự sử dụng nó. Tương tự như nó sẽ trông như thế nào nếu bạn sử dụng foreach (var v trong danh sách) sau. Nó sẽ vẫn chỉ chạy một lần. – Evelie

+0

Có, 'Where()' chỉ thực thi khi bạn lặp lại kết quả. Nhưng khi bạn làm điều đó, 'Where()' lặp lại bộ sưu tập gốc và 'ToList()' lặp lại bộ sưu tập được trả về từ 'Where()'. Vì vậy, bạn lặp lại hai lần. – svick

1

Trước hết, Why are you even asking me? Đo lường cho chính bạn và xem.

Điều đó nói rằng, Where, Select, OrderBy và phương pháp khuyến nông LINQ IEnumerable khác, nói chung, được thực hiện như lười biếng càng tốt (từ khóa yield được sử dụng thường xuyên). Điều đó có nghĩa là họ không làm việc trên dữ liệu trừ khi họ phải làm vậy. Từ ví dụ của bạn:

var list = Students.Where(s => s.Name == "ABC"); 

sẽ không thực hiện bất kỳ điều gì. Điều này sẽ trở lại trong giây lát ngay cả khi Students là danh sách 10 triệu đối tượng. Các vị ngữ sẽ không được gọi là ở tất cả cho đến khi kết quả là thực sự yêu cầu một nơi nào đó, và đó là thực tế những gì ToList() hiện: Nó nói "Có, kết quả - tất cả chúng - được yêu cầu ngay lập tức".Tuy nhiên, có một số chi phí ban đầu trong việc gọi các phương thức LINQ, vì vậy, cách truyền thống, nói chung, nhanh hơn, nhưng khả năng tương thích và dễ sử dụng các phương pháp LINQ, IMHO, bù đắp nhiều hơn cho cái đó.

Nếu bạn muốn xem các phương pháp này được triển khai như thế nào, chúng có sẵn để tham khảo từ Microsoft Reference Sources.

1

Để trả lời câu hỏi này hoàn toàn, tùy thuộc vào việc triển khai. Nếu bạn đang nói về LINQ to SQL/EF, sẽ chỉ có một lần lặp trong trường hợp này khi .ToList được gọi, mà gọi nội bộ .GetEnumerator. Biểu thức truy vấn sau đó được phân tích cú pháp thành TSQL và được chuyển tới cơ sở dữ liệu. Các hàng kết quả sau đó được lặp qua (một lần) và được thêm vào danh sách.

Trong trường hợp LINQ to Objects, chỉ có một lần truyền qua dữ liệu. Việc sử dụng trả về lợi nhuận trong mệnh đề where thiết lập một máy trạng thái nội bộ, theo dõi quá trình đang ở đâu trong quá trình lặp. Ở đâu KHÔNG làm lặp lại đầy đủ tạo danh sách tạm thời và sau đó chuyển các kết quả đó đến phần còn lại của truy vấn. Nó chỉ xác định nếu một mục đáp ứng một tiêu chí và chỉ vượt qua những mục phù hợp.

Các vấn đề liên quan