一次性返回所有可枚举的yield return; 不循环

发布于 2024-08-01 22:28:14 字数 417 浏览 14 评论 0 原文

我有以下函数来获取卡的验证错误。 我的问题涉及处理 GetErrors。 两种方法都具有相同的返回类型 IEnumerable

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

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>.

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

最舍不得你 2024-08-08 22:28:14

F# 支持整个集合的 yield! 和单个项目的 yield。 (这对于尾递归而言非常有用...)

不幸的是,C# 不支持它。

但是,如果您有多个方法,每个方法都返回 IEnumerable,则可以使用 Enumerable.Concat 使代码更简单:

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

两种实现之间有一个非常重要的区别不过:这个方法将立即调用所有方法,即使它一次只使用返回的迭代器。 您现有的代码将等到它循环遍历完 GetMoreErrors() 中的所有内容后,才会询问下一个错误。

通常这并不重要,但值得了解何时会发生什么。

It is 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...)

Unfortunately it's not supported in C#.

However, if you have several methods each returning an IEnumerable<ErrorInfo>, you can use Enumerable.Concat to make your code simpler:

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 asks about the next errors.

Usually this isn't important, but it's worth understanding what's going to happen when.

最单纯的乌龟 2024-08-08 22:28:14

您可以像这样设置所有错误源(方法名称借用 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 中方法的执行也会被延迟。

You could set up all the error sources like this (method names borrowed from Jon Skeet's answer).

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.

private static IEnumerable<ErrorInfo> GetErrors(Card card)
{
    return GetErrorSources(card).SelectMany(e => e);
}

The execution of the methods in GetErrorSources will be delayed too.

回忆那么伤 2024-08-08 22:28:14

是的,可以一次返回所有错误。 只需返回 ListReadOnlyCollection

通过返回 IEnumerable,您将返回一个序列。 从表面上看,这似乎与返回集合相同,但您应该记住有许多区别。

集合

  • 调用者可以确定集合返回时集合和所有项目都存在。 如果每次调用都必须创建集合,那么返回集合是一个非常糟糕的主意。
  • 大多数集合在返回时都可以修改。
  • 该集合的大小是有限的。

序列

  • 是可以枚举的——这几乎就是我们能肯定地说的全部内容。
  • 返回的序列本身无法修改。
  • 每个元素可以作为运行序列的一部分来创建(即返回IEnumerable允许延迟求值,而返回List则不允许)。
  • 序列可能是无限的,因此将其留给调用者来决定应返回多少个元素。

Yes it is possible to return all errors at once. Just return a List<T> or 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.

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, returning List<T> does not).
  • A sequence may be infinite and thus leave it to the caller to decide how many elements should be returned.
诗笺 2024-08-08 22:28:14

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

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 移动到调用站点。

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.

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.

冷情妓 2024-08-08 22:28:14

我没有看到你的功能有什么问题,我想说它正在做你想要的事情。

将 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;
    }
  }
}

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)

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...

public IEnumerable<string> ConcatLists(params IEnumerable<string>[] lists)
{
  foreach (IEnumerable<string> list in lists)
  {
    foreach (string s in list)
    {
      yield return s;
    }
  }
}
他不在意 2024-08-08 22:28:14

我想出了一个快速的 yield_ 片段:

yield_ snipped 使用动画

这是片段 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>

I came up with a quick yield_ snippet:

yield_ snipped usage animation

Here's the 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>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文