IEnumerable嵌套的收益率返回

我有以下function来获取卡的validation错误。 我的问题涉及到处理GetErrors。 两种方法都有相同的返回typesIEnumerable<ErrorInfo>

 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 } 

是否有可能返回在GetMoreErrors所有错误,而不必通过他们枚举?

思考这可能是一个愚蠢的问题,但我想确保我不会出错。

这绝对不是一个愚蠢的问题,而且F#支持yield! 对于单个项目的整个收集与yield 。 (这可以在尾recursion方面非常有用…)

不幸的是,它不被C#支持。

但是,如果您有多个方法返回一个IEnumerable<ErrorInfo> ,则可以使用Enumerable.Concat使代码更简单:

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

尽pipe这两个实现之间有一个非常重要的区别:这个实例会立即调用所有的方法,即使它只使用一次返回的迭代器。 您现有的代码将等待,直到它在GetMoreErrors()所有内容中循环,然后才会询问下一个错误。

通常这并不重要,但值得了解什么时候会发生。

你可以像这样设置所有的错误来源(方法名称来自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(); } 

然后你可以同时迭代它们。

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

或者,您可以使用SelectMany压扁错误来源。

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

GetErrorSources中的方法的执行也会被延迟。

我想出了一个快速yield_ snippet:

yield_剪切使用动画

以下是摘录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> 

我没有看到你的function有什么问题,我会说它正在做你想做的。

把Yield看作每次被调用时在Enumeration中返回一个元素,所以当你在foreach循环中这样做的时候,每次调用它时都会返回1个元素。 你可以在你的foreach中添加条件语句来过滤结果集。 (只要不排除您的排除标准)

如果稍后在方法中添加后续的yield,它将继续向枚举中添加1个元素,从而可以执行如下操作:

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

是的,可以一次返回所有错误。 只要返回一个List<T>ReadOnlyCollection<T>

通过返回一个IEnumerable<T>你返回一系列的东西。 表面看起来可能与返回collections相同,但有一些差异,请记住。

集合

  • 调用者可以确定集合返回时集合和所有项目都将存在。 如果集合必须在每次调用时创build,则返回集合是一个非常糟糕的主意。
  • 返回时大多数集合都可以修改。
  • 收集是有限的大小。

序列

  • 可以列举 – 这是我们可以肯定地说的。
  • 返回的序列本身不能被修改。
  • 每个元素都可以创build为运行序列的一部分(即,返回IEnumerable<T>允许惰性评估,返回List<T>不)。
  • 一个序列可能是无限的,因此让调用者决定应该返回多less个元素。

我很惊讶谁也没有想过推荐一个简单的扩展方法对IEnumerable<IEnumerable<T>>使这个代码保持其延期执行。 我是一个推迟执行的粉丝,原因很多,其中之一就是即使对于庞大的枚举types,内存占用也很小。

 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; } } } } 

你可以像这样在你的情况下使用它

 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 } 

同样的,你可以用DoGetErrors的wrapper函数来解决DoGetErrors然后把UnWrap移动到callsite上。