186

我有以下功能来获取卡的验证错误。我的问题与处理 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而不必枚举它们?

4

6 回答 6

160

这绝对不是一个愚蠢的问题,它是 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()然后才询问下一个错误。

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

于 2009-08-13T05:30:06.510 回答
33

您可以像这样设置所有错误源(从 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也会延迟。

于 2014-04-07T12:27:32.453 回答
20

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

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>
于 2015-06-14T18:16:17.893 回答
9

我很惊讶没有人想到推荐一个简单的扩展方法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到调用站点。

于 2016-09-22T14:20:59.360 回答
8

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

将 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;
    }
  }
}
于 2009-08-13T05:00:20.627 回答
3

是的,可以一次返回所有错误。只需返回一个List<T>or ReadOnlyCollection<T>

通过返回一个IEnumerable<T>你返回的东西的序列。从表面上看,这似乎与归还收藏品相同,但有许多不同之处,您应该记住。

收藏品

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

序列

  • 可以列举——这几乎就是我们可以肯定地说的所有内容。
  • 返回的序列本身不能被修改。
  • 每个元素都可以作为序列运行的一部分创建(即返回IEnumerable<T>允许延迟评估,返回List<T>不允许)。
  • 一个序列可能是无限的,因此由调用者决定应该返回多少元素。
于 2009-08-13T05:38:23.030 回答