c# return用法 - 带IEnumerable的嵌套收益率回报



yield作用 break (6)

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

考虑这可能是一个愚蠢的问题,但我想确保我不会出错。


Answers

我很惊讶没有人想过在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附近DoGetErrors包装功能, DoGetErrors移动到callsite。


这绝对不是一个愚蠢的问题,它是F#支持yield!的东西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()所有内容循环播放之后,它才会询问下一个错误。

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


我想出了一个快速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>

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


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

通过返回一个IEnumerable<T>你返回一系列的东西。 在表面上看起来可能与返回集合相同,但有一些差异,你应该记住。

集合

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

序列

  • 可以列举 - 这是我们可以肯定地说的很多事情。
  • 返回的序列本身不能被修改。
  • 每个元素都可以创建为运行序列的一部分(即,返回IEnumerable<T>允许惰性评估,返回List<T>不)。
  • 一个序列可能是无限的,因此让调用者决定应该返回多少个元素。

我们可以用同样的方式使用两者,但它们在性能上只有不同。

IQueryable仅以有效的方式针对数据库执行。 这意味着它会创建一个完整的选择查询并仅获取相关记录。

例如,我们想要选择名称以“Nimal”开头的前10位客户。 在这种情况下,select查询将生成为select top 10 * from Customer where name like 'Nimal%'

但是如果我们使用IEnumerable,查询就像select * from Customer where name like 'Nimal%' ,前十名将在C#编码级别进行过滤(它从数据库获取所有客户记录并将它们传递到C#中) 。





c# ienumerable yield yield-return