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