我有以下功能来获取卡的验证错误。我的问题与处理 GetErrors 有关。两种方法具有相同的返回类型 IEnumerable<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
用于单个项目。 (这在尾递归方面非常有用......)
不幸的是,它在 C# 中不受支持。
但是,如果您有多个方法,每个方法都返回一个 IEnumerable<ErrorInfo>
,您可以使用 Enumerable.Concat
来简化您的代码:
private static IEnumerable<ErrorInfo> GetErrors(Card card)
{
return GetMoreErrors(card).Concat(GetOtherErrors())
.Concat(GetValidationErrors())
.Concat(AnyMoreErrors())
.Concat(ICantBelieveHowManyErrorsYouHave());
}
不过,这两种实现之间有一个非常重要的区别:这个实现将立即调用所有方法,即使它一次只使用一个返回的迭代器。您现有的代码将等到它循环完 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_
片段:
https://i.stack.imgur.com/orXEZ.gif
这是片段 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>
yield!
,就像在 F# 中一样。
我看不出你的功能有什么问题,我会说它正在做你想做的事。
将 Yield 视为每次调用它时在最终 Enumeration 中返回一个元素,因此当您将它放在这样的 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;
}
}
}
我很惊讶没有人想到在 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
移动到调用站点。
DoGetErrors(card).SelectMany(x => x)
做了同样的事情并保留了延迟行为。这正是亚当在 his answer 中所建议的。
是的,可以一次返回所有错误。只需返回 List<T>
或 ReadOnlyCollection<T>
。
通过返回 IEnumerable<T>
,您将返回一系列内容。从表面上看,这似乎与归还收藏品相同,但有许多不同之处,您应该记住。
收藏品
调用者可以确定当集合返回时集合和所有项目都将存在。如果每次调用都必须创建集合,那么返回集合是一个非常糟糕的主意。
大多数集合可以在返回时进行修改。
集合的大小是有限的。
序列
可以列举——这几乎就是我们可以肯定地说的所有内容。
返回的序列本身不能被修改。
每个元素都可以作为运行序列的一部分创建(即返回 IEnumerable
一个序列可能是无限的,因此由调用者决定应该返回多少元素。
不定期副业成功案例分享
GetOtherErrors()
(等)正在推迟它们的 results (因为它们是使用迭代器块实现的)。尝试更改它们以返回一个新数组或类似的东西,你会明白我的意思。