2010-03-16 23 views
32

Hai phương pháp xuất hiện để hành xử như vậy với tôilà năng suất phá vỡ tương đương trở Enumerable <T> .Empty từ một phương pháp trở về IEnumerable <T>

public IEnumerable<string> GetNothing() 
{ 
    return Enumerable.Empty<string>(); 
} 

public IEnumerable<string> GetLessThanNothing() 
{ 
    yield break; 
} 

Tôi đã cấu hình mỗi trong các kịch bản thử nghiệm và tôi không thấy một sự khác biệt có ý nghĩa về tốc độ, nhưng phiên bản yield break nhanh hơn một chút.

Có bất kỳ lý do nào để sử dụng cái này cho người khác không? Là một trong những dễ đọc hơn khác? Có sự khác biệt về hành vi quan trọng với người gọi không?

Trả lời

30

Nếu bạn dự định luôn trả về số trống rỗng thì sử dụng cú pháp Enumerable.Empty<string>() là IMHO khai báo nhiều hơn.

Sự khác biệt về hiệu suất ở đây gần như chắc chắn không đáng kể. Tôi sẽ tập trung vào khả năng đọc trên hiệu suất ở đây cho đến khi một hồ sơ cho bạn thấy đó là một vấn đề.

+2

Ở đây và ở mọi nơi Khác (trừ khi một trình hồ sơ nói khác) –

1

Dường như yield break sẽ khởi tạo ít nhất một đối tượng ít hơn so với những gì return Enumerable.Empty<string>() sẽ làm. Ngoài ra, có thể có một số kiểm tra rằng bạn sẽ bị đoản mạch với yield break. Và nếu không có gì khác, đó là một wrapper ít chức năng mà ngăn xếp của bạn đi qua mà sẽ được phát hiện mặc dù không đáng chú ý.

Tuy nhiên, tôi đồng ý với câu trả lời khác được đăng rằng .Empty là cách "ưa thích" để thực hiện việc này.

+8

Tại sao bạn lại nghĩ vậy? 'Enumerable.Empty ' có thể trả về cùng một điều mỗi lần - và tôi tin nó thực tế. Không cần thiết phải tạo ra gì cả, sau cuộc gọi đầu tiên. Tôi nghi ngờ rất nhiều rằng trình biên dịch C# sẽ phát hiện ra rằng nó có thể làm điều đó cho trường hợp 'yield break'. –

+1

@Jon Skeet - Bạn nói đúng. Phiên bản ngắt lãi suất thực sự là instantiating lớp 'yield' tạo ra mỗi lần. 'Enumerable.Empty' đủ thông minh để cache –

+0

Nó sẽ không phải trả lại ít nhất một bản sao nhân bản (tức là một phiên bản mới)? – Jaxidian

9

IEnumerable<T> phương pháp với yield break hoặc yield return trong cơ thể chúng được chuyển thành máy trạng thái. Trong loại phương pháp này, bạn không thể kết hợp lợi nhuận với lợi nhuận truyền thống. Ý tôi là nếu bạn mang một thứ gì đó vào một phần nào đó của phương pháp, bạn không thể trả lại ICollection ở một phương thức khác.

Mặt khác, giả sử bạn đang triển khai phương thức có kiểu trả về IEnumerable<T> bằng cách thêm các mục vào bộ sưu tập và sau đó trả lại bản sao chỉ đọc của bộ sưu tập. Nếu vì lý do nào đó mà bạn muốn trả lại bộ sưu tập trống, bạn không thể thực hiện yield break. Tất cả những gì bạn có thể làm chỉ là trả lại Enumerable.Empty<T>().

Nếu bạn đã cấu hình cả hai cách, và không có thay đổi đáng kể, sau đó bạn có thể chỉ cần quên nó đi :)

+0

+1 để làm rõ cách tiếp cận chung/số lượng/năng suất so với phương pháp xây dựng bộ sưu tập, tức là mỗi phương pháp * bắt buộc * một lý do để sử dụng phương pháp này hoặc phương pháp khác. – downwitch

+0

Id cho biết sản lượng IEnumerable hoạt động như "Trình tạo Python có thể được nhắc lại". Ưu điểm của IEnumerable không phải là MemLoad toàn bộ danh sách hoặc kết quả cùng một lúc, do đó, việc sử dụng hiệu suất luôn hiệu quả hơn là tạo toàn bộ danh sách hoặc mảng và trả về nó – Felype

5

điều Funny, tôi đọc bài này sáng nay, và vài giờ sau đó đập vào mắt tôi của thành viên này ví dụ - đã tìm thấy sự khác biệt khi bạn có nhiều mã hơn:

public static IEnumerable<T> CoalesceEmpty<T>(IEnumerable<T> coll) 
{ 
    if (coll == null) 
     return Enumerable.Empty<T>(); 
    else 
     return coll; 
} 

Bạn không thể thay đổi lần trả lại đầu tiên cho kỳ nghỉ, vì bạn sẽ phải thay đổi lần thứ hai trở lại (sang phiên bản dài hơn).

5

Tôi đã lược tả từng trường hợp trong thử nghiệm và tôi không thấy sự khác biệt có ý nghĩa về tốc độ, nhưng phiên bản ngắt sản lượng nhanh hơn một chút.

Tôi sẽ đoán rằng các thử nghiệm lược tả của bạn không bao gồm tốc độ khởi động chương trình. Các yield xây dựng các công trình bằng cách tạo ra một lớp học cho bạn. Mã bổ sung này là tuyệt vời khi nó cung cấp logic bạn cần, nhưng nếu không, nó chỉ thêm vào đĩa I/O, kích thước thiết lập làm việc và thời gian JIT.

Nếu bạn mở một chương trình chứa phương pháp thử trong ILSpy và tắt tính toán số đếm, bạn sẽ tìm thấy một lớp có tên <GetLessThanNothing>d__0 với một tá thành viên trở lên. phương pháp MoveNext của nó trông như thế này:

bool IEnumerator.MoveNext() 
{ 
    int num = this.<>1__state; 
    if (num == 0) 
    { 
     this.<>1__state = -1; 
    } 
    return false; 
} 

EmptyEnumerable hoạt động bằng cách uể oải tạo ra một mảng trống rỗng tĩnh. Có lẽ việc kiểm tra xem mảng có cần phải được tạo ra hay không là lý do EmptyEnumerable chậm hơn yield break trong điểm chuẩn bị cô lập, nhưng có thể sẽ mất rất nhiều lần lặp lại để khắc phục hình phạt khởi động, và cả hai cách sẽ khó có thể nhận thấy tổng thể, ngay cả trong một kịch bản "cái chết của một nghìn perf papercuts".

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