立即返回所有具有返回值的可枚举数; 不要循环

我有以下函数来获取卡的验证错误。我的问题与处理 GetErerror 有关。两个方法的返回类型相同。

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中的所有错误而不必枚举它们?

49968 次浏览

我看不出你的功能有什么问题,我只能说它正在做你想做的事情。

可以将屈服看作是每次调用最终枚举时返回一个元素,因此当在 foreach 循环中使用屈服时,每次调用都返回1个元素。您可以在 foreach 中放置条件语句来过滤结果集。(只要不屈服于你的排除标准)

如果您稍后在该方法中添加后续的产量,它将继续向枚举中添加1个元素,从而可以执行..。

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

对于整个集合,F # 支持 yield!,对于单个项目,支持 yield。(这在尾递归方面非常有用...)

不幸的是,C # 不支持。

但是,如果有多个方法,每个方法返回一个 IEnumerable<ErrorInfo>,那么可以使用 Enumerable.Concat来简化代码:

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

但是这两个实现之间有一个非常重要的区别: 这个实现将调用所有的方法 马上,即使它一次只使用一个返回的迭代器。您现有的代码将等待,直到它循环遍历 GetMoreErrors()中的所有内容,甚至在 问道发生下一个错误之前。

通常情况下,这并不重要,但是我们有必要了解一下。

是的,可以同时返回所有错误。只需返回一个 List<T>或者 ReadOnlyCollection<T>

返回一个 IEnumerable<T>就等于返回一个序列。表面上看起来可能与返回集合相同,但是有很多不同之处,您应该记住。

收款

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

序列

  • 可以被列举出来——而这几乎是我们能够肯定的全部。
  • 不能修改返回的序列本身。
  • 每个元素都可以作为运行序列的一部分创建(即返回 IEnumerable<T>允许延迟计算,返回 List<T>则不允许)。
  • 一个序列可能是无限的,因此留给调用者来决定应该返回多少个元素。

您可以像这样设置所有的错误源(方法名称借用了 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_片段:

yield_ snipped usage animation

下面是 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>

令我惊讶的是,没有人想到在 IEnumerable<IEnumerable<T>>上推荐一个简单的扩展方法来保持这段代码的延迟执行。我喜欢延迟执行的原因有很多,其中之一就是即使对于庞大的可枚举数来说,内存占用也很小。

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周围的包装函式,只需将 UnWrap移动到 callsite。