4
public static IEnumerable<T> Pipe<T>(this IEnumerable<T> source, Action<T> action)
{    
    return _(); IEnumerable <T> _()
    {
        foreach (var element in source)
        {
            action(element);
            yield return element;
        }
    }
}

我在 MoreLinq repo 中找到了这段代码,但无法理解这一行:

return _(); IEnumerable <T> _()
4

2 回答 2

6

这段代码使用了 C# 的一个相对较新的特性,称为本地函数。这个函数唯一不寻常的是它的名字:开发人员使用了一个下划线。因此,函数的名称是_,所以调用看起来像这样:_()

既然您知道该return语句返回调用名为 的本地函数的结果,_那么语法的其余部分就到位了:

// This is a local function
IEnumerable <T> _() {
    ...
}

(OP对这个问题的评论)我们不能只做foreachyield return

您复制的方法包括另外两行,这是理解差异的关键:

public static IEnumerable<T> Pipe<T>(this IEnumerable<T> source, Action<T> action)
{    
    if (source == null) throw new ArgumentNullException(nameof(source));
    if (action == null) throw new ArgumentNullException(nameof(action));
    return _(); IEnumerable <T> _()
    {
        foreach (var element in source)
        {
            action(element);
            yield return element;
        }
    }
}

如果您将foreachwithyield return直接放入Pipe<T>方法体中,则参数检查将被推迟,直到您开始迭代IEnumerable<T>结果。使用本地函数,您将在调用时立即进行检查Pipe<T>,即使在调用者从不迭代结果的情况下也是如此。

于 2017-12-27T11:04:00.877 回答
3

我是MoreLINQ的维护者。下面,我引用了一个拉取请求,它将为您提供使用本地函数的背景:

此 PR 的目的是将所有运算符的私有迭代器方法(在可能的情况下)重构为本地函数(由 C# 7 引入)。到目前为止,需要拆分以便在调用运算符方法时而不是在第一次使用迭代器时(可能远离调用站点)时急切地检查参数。现在可以通过本地函数完成相同的拆分,并具有代码简化的额外好处,例如:

  • 父方法的参数在范围内,因此实际迭代器实现方法的签名变得更简单,因为不需要传递所有参数。
  • 父方法的类型参数在范围内,所以不需要重复。
  • 可以少写一种顶级方法。
  • 迭代器主体出现在使用它的实际公共运算符方法中。
  • 对于已经检查过的参数,不需要调试构建断言

为了回答样式的选择return _(); IEnumerable <T> _(),我将引用我在项目的pull request #360中提供的基本原理:

将 return 语句和本地函数声明放在一行中旨在弥补语言不简洁地支持匿名迭代器的事实。该行没有提供额外的信息或上下文,因此将两者分开并没有获得明确性,除了它可能在样式上看起来有点不正统。想想有多少事情是无关紧要的:

  • 本地迭代器函数的名称,因此它被赋予_.
  • 本地函数的返回类型,因为它与外部方法的返回类型是多余的。
  • 对本地函数的调用永远不会真正执行,因为迭代器函数变得懒惰,因此单独突出显示调用甚至有点误导。

实际上返回的是一个带有算法主体的迭代器对象,因此将其全部放在一行上的风格旨在使其看起来像那样。

造型的起源和玩法来自这样的想法……

如果你足够仔细地眯着眼睛,你几乎可以相信 C# 7 现在有了匿名迭代器……</p>

C# 7 中的匿名迭代器,几乎

另请参阅#291中的一些示例。

于 2018-05-19T12:59:09.183 回答