我有以下功能来获取卡的验证错误。我的问题涉及处理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
中返回所有错误,而无需通过它们进行枚举?
考虑到这可能是一个愚蠢的问题,但我想确保我不会出错。
答案 0 :(得分:130)
这绝对不是一个愚蠢的问题,而且F#支持整个集合的yield!
与单个项目的yield
。IEnumerable<ErrorInfo>
。 (这在尾递归方面非常有用......)
不幸的是,C#不支持它。
但是,如果您有多个方法都返回Enumerable.Concat
,则可以使用private static IEnumerable<ErrorInfo> GetErrors(Card card)
{
return GetMoreErrors(card).Concat(GetOtherErrors())
.Concat(GetValidationErrors())
.Concat(AnyMoreErrors())
.Concat(ICantBelieveHowManyErrorsYouHave());
}
使代码更简单:
GetMoreErrors()
这两个实现之间有一个非常重要的区别:这个方法会立即调用所有方法 ,即使它一次只使用一个返回的迭代器。您的现有代码将等到{{1}}中的所有内容循环,直到询问有关下一个错误。
通常这并不重要,但值得了解将会发生什么。
答案 1 :(得分:19)
您可以像这样设置所有错误来源(从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
中方法的执行也会延迟。
答案 2 :(得分:13)
我提出了一个快速的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>
答案 3 :(得分:7)
我认为你的功能没有任何问题,我会说它正在做你想要的。
将Yield视为在每次调用它时在最终枚举中返回一个元素,所以当你在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;
}
}
}
答案 4 :(得分:3)
是的,可以立即返回所有错误。只需返回List<T>
或ReadOnlyCollection<T>
即可。
通过返回IEnumerable<T>
,您将返回一系列内容。表面上可能看起来与返回集合相同,但有许多区别,你应该记住。
集合
序列
IEnumerable<T>
允许进行延迟评估,返回List<T>
则不会。)答案 5 :(得分:3)
我很惊讶没有人想过在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
移到调用点。