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> _()
答案 0 :(得分:5)
此代码使用C#的一个相对较新的功能,称为local function。关于这个函数唯一不寻常的是它的名字:开发人员使用了一个下划线。因此,函数的名称是_
,因此调用如下所示:_()
既然你知道return
语句返回调用名为_
的本地函数的结果,那么语法的其余部分就会出现:
// This is a local function
IEnumerable <T> _() {
...
}
(OP对该问题的评论)我们不能
foreach
与yield 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;
}
}
}
如果您将带有foreach
的{{1}}直接放入yield return
方法的正文中,则会推迟参数检查,直到您开始迭代Pipe<T>
结果为止。有了本地函数,只要调用IEnumerable<T>
,就会立即进行检查,即使在调用者从不迭代结果的情况下也是如此。
答案 1 :(得分:2)
我是MoreLINQ的维护者。下面,我引用了a pull request,它将为您提供使用本地函数背后的背景:
此PR的目的是将所有运算符的私有迭代器方法(如果可能)重构为local functions (introduced with C# 7)。到目前为止需要进行拆分,以便在调用operator方法时急切地检查参数,而不是在第一次使用迭代器时(可能远离调用站点)。现在可以通过本地函数完成相同的拆分,并具有代码简化的附加好处,如:
- 父方法的参数在范围内,因此实际迭代器实现方法的签名变得更简单,因为不需要传递所有参数。
- 父方法的类型参数在范围内,因此不需要重复。
- 有一种较少的顶级方法可以写。
- 迭代器主体与使用它的实际公共运算符方法串联。
- 不需要对已经检查过的参数进行调试构建断言
要回答样式return _(); IEnumerable <T> _()
的选择,我将引用我在pull request #360中为项目提供的理由:
将return语句和本地函数声明放在一行是为了弥补语言不简洁地支持匿名迭代器这一事实。在该行上没有提供额外的信息或上下文,因此除了在样式上看起来有点不正统之外,两者之间没有明确的区别。只考虑有多少事情是无关紧要的:
- 本地迭代器函数的名称,因此给出了伪名称
_
。- 本地函数的返回类型,因为它与外部方法的返回类型是多余的。
- 对本地函数的调用永远不会真正执行,因为迭代器函数变得懒惰,所以它甚至有点误导性地突出显示该调用。
实际上返回的是具有算法主体的迭代器对象,因此将它全部放在一行上的样式旨在使其看起来就像那样。
造型的起源和发挥来自......
的想法如果你眯着眼睛,你几乎可以相信C#7现在有anonymous iterators ......
另请参阅#291中的一些示例。