2009-08-13 23 views
119

Tôi có chức năng sau để nhận các lỗi xác thực cho thẻ. Câu hỏi của tôi liên quan đến giao dịch với GetErrors. Cả hai phương thức đều có cùng kiểu trả về IEnumerable<ErrorInfo>.Thu nhập lãi suất lồng nhau với IEnumerable

private static IEnumerable<ErrorInfo> GetErrors(Card card) 
{ 
    var errors = GetMoreErrors(card); 
    foreach (var e in errors) 
     yield return e; 

    // further yield returns for more validation errors 
} 

Có thể trả lại tất cả các lỗi trong GetMoreErrors mà không cần phải liệt kê qua chúng?

Nghĩ về điều này, đây có lẽ là một câu hỏi ngu ngốc, nhưng tôi muốn đảm bảo rằng tôi không đi sai.

+0

Tôi rất vui (và tò mò!) Để xem thêm câu trả lời về lợi nhuận - tôi không hoàn toàn hiểu chính mình. Không phải là một câu hỏi ngu ngốc! – JoshJordan

+0

'GetCardProductionValidationErrorsFor' là gì? –

+4

có vấn đề gì với * trả lại GetMoreErrors (thẻ); *? –

Trả lời

107

Nó chắc chắn không phải là một câu hỏi ngu ngốc, và nó là một cái gì đó mà F # hỗ trợ với yield! cho một bộ sưu tập toàn bộ vs yield cho một mục duy nhất. (Điều đó có thể rất hữu ích trong việc đệ quy đuôi ...)

Rất tiếc, nó không được hỗ trợ trong C#.

Tuy nhiên, nếu bạn có một số phương pháp từng trả lại một IEnumerable<ErrorInfo>, bạn có thể sử dụng Enumerable.Concat để làm cho mã của bạn đơn giản hơn:

private static IEnumerable<ErrorInfo> GetErrors(Card card) 
{ 
    return GetMoreErrors(card).Concat(GetOtherErrors()) 
           .Concat(GetValidationErrors()) 
           .Concat(AnyMoreErrors()) 
           .Concat(ICantBelieveHowManyErrorsYouHave()); 
} 

Có một sự khác biệt rất quan trọng giữa hai triển khai mặc dù: cái này sẽ gọi tất cả các phương thức ngay lập tức, mặc dù nó sẽ chỉ sử dụng các trình vòng lặp được trả về một tại một thời điểm. Mã hiện tại của bạn sẽ đợi cho đến khi mã được lặp qua tất cả mọi thứ trong GetMoreErrors() trước khi nó ngay cả yêu cầu về các lỗi tiếp theo.

Thông thường điều này không quan trọng, nhưng đáng để hiểu điều gì sẽ xảy ra khi nào.

+2

Wes Dyer có một bài viết thú vị đề cập đến mô hình này. http://blogs.msdn.com/wesdyer/archive/2007/03/23/all-about-iterators.aspx – JohannesH

+1

Chỉnh sửa nhỏ cho người qua đường - đó là System.Linq.Enumeration.Concat <> (thứ nhất, thứ hai) . Không phải IEnumeration.Concat(). – redcalx

+0

@ the-locster: Tôi không chắc chắn ý của bạn là gì. Đó chắc chắn là Enumerable chứ không phải là Enumeration. Bạn có thể làm rõ bình luận của bạn? –

6

Tôi không thấy bất cứ điều gì sai với chức năng của bạn, tôi muốn nói rằng nó đang làm những gì bạn muốn.

Hãy suy nghĩ về lợi nhuận khi trả về một phần tử trong liệt kê cuối cùng mỗi lần nó được gọi, vì vậy khi bạn có nó trong vòng lặp foreach như thế, mỗi lần được gọi nó trả về 1 phần tử. Bạn có khả năng đặt câu lệnh có điều kiện trong foreach của bạn để lọc resultset. (Chỉ đơn giản bằng cách không năng suất trên các tiêu chí loại trừ bạn)

Nếu bạn thêm lãi suất tiếp theo sau trong phương pháp, nó sẽ tiếp tục thêm 1 yếu tố để liệt kê, làm cho nó có thể làm những việc như ...

public IEnumerable<string> ConcatLists(params IEnumerable<string>[] lists) 
{ 
    foreach (IEnumerable<string> list in lists) 
    { 
    foreach (string s in list) 
    { 
     yield return s; 
    } 
    } 
} 
5

Có thể trả lại tất cả các lỗi cùng một lúc. Chỉ cần trả lại List<T> hoặc ReadOnlyCollection<T>.

Bằng cách trả lại số IEnumerable<T>, bạn sẽ trả về chuỗi thứ gì đó. Trên bề mặt có vẻ giống hệt để trả lại bộ sưu tập, nhưng có một số khác biệt, bạn nên ghi nhớ.

Collections

  • Người gọi có thể chắc chắn rằng cả hai bộ sưu tập và tất cả các mục sẽ tồn tại khi các bộ sưu tập được trả về. Nếu bộ sưu tập phải được tạo cho mỗi cuộc gọi, việc trả lại bộ sưu tập thực sự là một ý tưởng tồi.
  • Hầu hết các bộ sưu tập đều có thể được sửa đổi khi được trả về.
  • Bộ sưu tập có kích thước hữu hạn.

Sequences

  • Có thể liệt kê - và đó là khá nhiều tất cả chúng ta có thể nói chắc chắn.
  • Không thể sửa đổi chính chuỗi đã trả về.
  • Mỗi phần tử có thể được tạo thành một phần của quá trình chạy qua chuỗi (tức là trả lại IEnumerable<T> cho phép đánh giá lười, trả lại List<T> thì không).
  • Một chuỗi có thể là vô hạn và do đó hãy để cho người gọi quyết định có bao nhiêu phần tử cần được trả về.
+0

Trả về một bộ sưu tập có thể dẫn đến chi phí không hợp lý nếu tất cả các khách hàng thực sự cần phải liệt kê thông qua nó, vì bạn phân bổ cấu trúc dữ liệu cho tất cả các phần tử trước. Ngoài ra, nếu bạn ủy thác cho một phương thức khác trả về một chuỗi, thì việc capture nó như là một bộ sưu tập liên quan đến việc sao chép thêm, và bạn không biết có bao nhiêu mục (và do đó có bao nhiêu chi phí). Vì vậy, nó chỉ là một ý tưởng tốt để trả lại bộ sưu tập khi nó đã có và có thể được trả về trực tiếp mà không cần sao chép (hoặc được bao bọc như chỉ đọc). Trong tất cả các trường hợp khác, trình tự là lựa chọn tốt hơn –

+0

Tôi đồng ý và nếu bạn có ấn tượng rằng tôi đã trả lại bộ sưu tập thì luôn là ý tưởng hay vì bạn đã bỏ lỡ quan điểm của mình. Tôi đã cố gắng làm nổi bật thực tế rằng có sự khác biệt giữa việc trả về một bộ sưu tập và trả về một chuỗi. Tôi sẽ cố gắng làm cho nó rõ ràng hơn. –

14

Bạn có thể thiết lập tất cả các nguồn lỗi như thế này (tên phương thức được mượn từ câu trả lời của Jon Skeet).

private static IEnumerable<IEnumerable<ErrorInfo>> GetErrorSources(Card card) 
{ 
    yield return GetMoreErrors(card); 
    yield return GetOtherErrors(); 
    yield return GetValidationErrors(); 
    yield return AnyMoreErrors(); 
    yield return ICantBelieveHowManyErrorsYouHave(); 
} 

Sau đó, bạn có thể lặp lại chúng cùng một lúc.

private static IEnumerable<ErrorInfo> GetErrors(Card card) 
{ 
    foreach (var errorSource in GetErrorSources(card)) 
     foreach (var error in errorSource) 
      yield return error; 
} 

Hoặc bạn có thể làm phẳng các nguồn lỗi với SelectMany.

private static IEnumerable<ErrorInfo> GetErrors(Card card) 
{ 
    return GetErrorSources(card).SelectMany(e => e); 
} 

Việc thực hiện các phương pháp trong GetErrorSources cũng sẽ bị trì hoãn.

9

tôi đã đưa ra một đoạn nhanh chóng yield_:

yield_ snipped usage animation

Dưới đây là đoạn mã XML:

<?xml version="1.0" encoding="utf-8"?> 
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet"> 
    <CodeSnippet Format="1.0.0"> 
    <Header> 
     <Author>John Gietzen</Author> 
     <Description>yield! expansion for C#</Description> 
     <Shortcut>yield_</Shortcut> 
     <Title>Yield All</Title> 
     <SnippetTypes> 
     <SnippetType>Expansion</SnippetType> 
     </SnippetTypes> 
    </Header> 
    <Snippet> 
     <Declarations> 
     <Literal Editable="true"> 
      <Default>items</Default> 
      <ID>items</ID> 
     </Literal> 
     <Literal Editable="true"> 
      <Default>i</Default> 
      <ID>i</ID> 
     </Literal> 
     </Declarations> 
     <Code Language="CSharp"><![CDATA[foreach (var $i$ in $items$) yield return $i$$end$;]]></Code> 
    </Snippet> 
    </CodeSnippet> 
</CodeSnippets> 
1

Tôi ngạc nhiên không ai nghĩ đến đề nghị một phương pháp mở rộng đơn giản trên IEnumerable<IEnumerable<T>> để làm cho mã này giữ cho hoạt động trì hoãn của nó. Tôi là một fan hâm mộ của thực thi hoãn lại vì nhiều lý do, một trong số đó là do bộ nhớ còn nhỏ ngay cả đối với những người khổng lồ.

public static class EnumearbleExtensions 
{ 
    public static IEnumerable<T> UnWrap<T>(this IEnumerable<IEnumerable<T>> list) 
    { 
     foreach(var innerList in list) 
     { 
      foreach(T item in innerList) 
      { 
       yield return item; 
      } 
     } 
    } 
} 

Và bạn có thể sử dụng nó trong trường hợp của bạn như thế này

private static IEnumerable<ErrorInfo> GetErrors(Card card) 
{ 
    return DoGetErrors(card).UnWrap(); 
} 

private static IEnumerable<IEnumerable<ErrorInfo>> DoGetErrors(Card card) 
{ 
    yield return GetMoreErrors(card); 

    // further yield returns for more validation errors 
} 

Tương tự như vậy, bạn có thể loại bỏ các chức năng bao bọc xung quanh DoGetErrors và chỉ di chuyển UnWrap đến callsite.

+0

Có lẽ không ai nghĩ về một phương pháp mở rộng vì 'DoGetErrors (thẻ) .SelectMany (x => x)' làm như vậy và duy trì hành vi trì hoãn. Đó là chính xác những gì Adam gợi ý trong [câu trả lời của anh] (http://stackoverflow.com/a/22912410/1300910). –

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