C# 立即返回所有具有 yield return 的可枚举数;无需循环
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1270024/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Return all enumerables with yield return at once; without looping through
提问by John Oxley
I have the following function to get validation errors for a card. My question relates to dealing with GetErrors. Both methods have the same return type IEnumerable<ErrorInfo>
.
我有以下功能来获取卡片的验证错误。我的问题与处理 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
}
Is it possible to return all the errors in GetMoreErrors
without having to enumerate through them?
是否可以返回所有错误GetMoreErrors
而不必枚举它们?
Thinking about it this is probably a stupid question, but I want to make sure I'm not going wrong.
想想这可能是一个愚蠢的问题,但我想确保我没有出错。
采纳答案by Jon Skeet
It's definitely not a stupid question, and it's something that F# supports with yield!
for a whole collection vs yield
for a single item. (That can be very useful in terms of tail recursion...)
这绝对不是一个愚蠢的问题,而且 F# 支持yield!
整个集合yield
而不是单个项目。(这在尾递归方面非常有用......)
Unfortunately it's not supported in C#.
不幸的是,它在 C# 中不受支持。
However, if you have several methods each returning an IEnumerable<ErrorInfo>
, you can use Enumerable.Concat
to make your code simpler:
但是,如果您有多个方法,每个方法都返回IEnumerable<ErrorInfo>
,则可以使用Enumerable.Concat
使代码更简单:
private static IEnumerable<ErrorInfo> GetErrors(Card card)
{
return GetMoreErrors(card).Concat(GetOtherErrors())
.Concat(GetValidationErrors())
.Concat(AnyMoreErrors())
.Concat(ICantBelieveHowManyErrorsYouHave());
}
There's one very important difference between the two implementations though: this one will call all of the methods immediately, even though it will only use the returned iterators one at a time. Your existing code will wait until it's looped through everything in GetMoreErrors()
before it even asksabout the next errors.
不过,这两种实现之间有一个非常重要的区别:这种实现会立即调用所有方法,即使它一次只使用一个返回的迭代器。您现有的代码将等到它循环遍历所有内容,GetMoreErrors()
然后才会询问下一个错误。
Usually this isn't important, but it's worth understanding what's going to happen when.
通常这并不重要,但值得了解什么时候会发生什么。
回答by Tim Jarvis
I don't see anything wrong with your function, I'd say that it is doing what you want.
我看不出你的功能有什么问题,我会说它正在做你想做的事。
Think of the Yield as returning an element in the final Enumeration each time that it is invoked, so when you have it in the foreach loop like that, each time it is invoked it returns 1 element. You have the ability to put conditional statements in your foreach to filter the resultset. (simply by not yielding on your exclusion criteria)
将 Yield 视为每次调用时在最终 Enumeration 中返回一个元素,因此当您像这样在 foreach 循环中使用它时,每次调用它时都会返回 1 个元素。您可以在 foreach 中放置条件语句来过滤结果集。(只是不屈服于您的排除标准)
If you add subsequent yields later in the method, it will continue to add 1 element to the enumeration, making it possible to do things like...
如果您稍后在方法中添加后续收益,它将继续向枚举添加 1 个元素,从而可以执行诸如...
public IEnumerable<string> ConcatLists(params IEnumerable<string>[] lists)
{
foreach (IEnumerable<string> list in lists)
{
foreach (string s in list)
{
yield return s;
}
}
}
回答by Brian Rasmussen
Yes it is possible to return all errors at once. Just return a List<T>
or ReadOnlyCollection<T>
.
是的,可以一次返回所有错误。只需返回一个List<T>
或ReadOnlyCollection<T>
。
By returning an IEnumerable<T>
you're returning a sequence of something. On the surface that may seem identical to returning the collection, but there are a number of difference, you should keep in mind.
通过返回 anIEnumerable<T>
你正在返回一个序列的东西。从表面上看,这似乎与返回集合相同,但有许多不同之处,您应该记住。
Collections
收藏
- The caller can be sure that both the collection and all the items will exist when the collection is returned. If the collection must be created per call, returning a collection is a really bad idea.
- Most collections can be modified when returned.
- The collection is of finite size.
- 调用者可以确保在返回集合时集合和所有项目都将存在。如果每次调用都必须创建集合,那么返回一个集合是一个非常糟糕的主意。
- 大多数集合在返回时可以修改。
- 集合的大小是有限的。
Sequences
序列
- Can be enumerated - and that is pretty much all we can say for sure.
- A returned sequence itself cannot be modified.
- Each element may be created as part of running through the sequence (i.e. returning
IEnumerable<T>
allows for lazy evaluation, returningList<T>
does not). - A sequence may be infinite and thus leave it to the caller to decide how many elements should be returned.
- 可以列举 - 这几乎是我们可以肯定地说的全部。
- 不能修改返回的序列本身。
- 每个元素都可以作为遍历序列的一部分创建(即返回
IEnumerable<T>
允许延迟评估,返回List<T>
不允许)。 - 一个序列可能是无限的,因此由调用者决定应该返回多少个元素。
回答by Adam Boddington
You could set up all the error sources like this (method names borrowed from Jon Skeet's answer).
您可以像这样设置所有错误源(从 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();
}
You can then iterate over them at the same time.
然后,您可以同时迭代它们。
private static IEnumerable<ErrorInfo> GetErrors(Card card)
{
foreach (var errorSource in GetErrorSources(card))
foreach (var error in errorSource)
yield return error;
}
Alternatively you could flatten the error sources with SelectMany
.
或者,您可以使用SelectMany
.
private static IEnumerable<ErrorInfo> GetErrors(Card card)
{
return GetErrorSources(card).SelectMany(e => e);
}
The execution of the methods in GetErrorSources
will be delayed too.
方法的执行GetErrorSources
也会被延迟。
回答by John Gietzen
I came up with a quick yield_
snippet:
我想出了一个快速yield_
片段:
Here's the snippet XML:
这是 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>
回答by Frank Bryce
I'm surprised no one has thought to recommend a simple Extension method on IEnumerable<IEnumerable<T>>
to make this code keep its deferred execution. I'm a fan of deferred execution for many reasons, one of them is that the memory footprint is small even for huge-mongous enumerables.
我很惊讶没有人想到推荐一种简单的扩展方法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;
}
}
}
}
And you could use it in your case like this
你可以像这样在你的情况下使用它
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
}
Similarly, you can do away with the wrapper function around DoGetErrors
and just move UnWrap
to the callsite.
类似地,您可以取消周围的包装函数,DoGetErrors
而只需移至UnWrap
调用站点。