谁能解释这个枚举器语法?
Can anyone explain this enumerator syntax?
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 存储库中找到了这段代码,但无法理解这一行:
return _(); IEnumerable <T> _()
此代码使用了 C# 的一项相对较新的功能,称为 local function。这个函数唯一不同寻常的地方在于它的名字:开发人员为它使用了一个下划线。因此,函数的名称是 _
,所以调用看起来像这样:_()
现在您知道 return
语句 returns 是调用名为 _
的本地函数的结果,其余语法就位:
// This is a local function
IEnumerable <T> _() {
...
}
(OP's comment on the question) Can't we just do foreach
with 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
和 yield return
直接放入 Pipe<T>
方法的主体中,参数检查将延迟到您开始迭代 IEnumerable<T>
结果。有了本地函数,您将在调用 Pipe<T>
后立即进行检查,即使在调用者从不迭代结果的情况下也是如此。
我是 MoreLINQ. Below, I am quoting from a pull request 的维护者,将为您介绍使用本地函数的背景:
The purpose of this PR is to refactor the private iterator methods of all operators (where possible) as local functions (introduced with C# 7). The split was needed up until now in order to have arguments checked eagerly, when the operator method is invoked and not when the iterator is used the first time (which may be far from the call site). The same split can now be done via a local function with the added benefit of code simplification like:
- parameters of parent method are in scope so the signature of the actual iterator implementation method becomes simpler since there is no need to pass on all arguments.
- type parameters of parent method are in scope so don't need repeating.
- there is one less top-level method to write.
- iterator body appears in-line to the actual public operator method that uses it.
- there is no need for debug-build assertions for arguments that have already been checked
为了回答样式选择 return _(); IEnumerable <T> _()
,我将引用我在 pull request #360 中提供给项目的基本原理:
Putting the return statement and the local function declaration on a single line is designed to compensate for the fact that the language doesn't support anonymous iterators succinctly. There is no extra information or context being provided on that line so there's no clarity gained by separating the two except that it might appear a little unorthodox in styling. Just consider how many things are immaterial:
- The name of the local iterator function so it is given the bogus name of
_
.
- The return type of the local function because it's redundant with the outer method's return type.
- The call to the local function never really executes because the iterator function becomes lazy so it's even somewhat misleading to highlight the call on its own.
What's in fact being returned is an iterator object with the body of its algorithm and so the style of putting it all on a single line is designed to make it appear just as that.
The origin and play on the styling come from the idea that…
If you squint hard enough, you can almost believe that C# 7 now has anonymous
iterators…
另请参阅 #291 中的一些示例。
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 存储库中找到了这段代码,但无法理解这一行:
return _(); IEnumerable <T> _()
此代码使用了 C# 的一项相对较新的功能,称为 local function。这个函数唯一不同寻常的地方在于它的名字:开发人员为它使用了一个下划线。因此,函数的名称是 _
,所以调用看起来像这样:_()
现在您知道 return
语句 returns 是调用名为 _
的本地函数的结果,其余语法就位:
// This is a local function
IEnumerable <T> _() {
...
}
(OP's comment on the question) Can't we just do
foreach
withyield 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
和 yield return
直接放入 Pipe<T>
方法的主体中,参数检查将延迟到您开始迭代 IEnumerable<T>
结果。有了本地函数,您将在调用 Pipe<T>
后立即进行检查,即使在调用者从不迭代结果的情况下也是如此。
我是 MoreLINQ. Below, I am quoting from a pull request 的维护者,将为您介绍使用本地函数的背景:
The purpose of this PR is to refactor the private iterator methods of all operators (where possible) as local functions (introduced with C# 7). The split was needed up until now in order to have arguments checked eagerly, when the operator method is invoked and not when the iterator is used the first time (which may be far from the call site). The same split can now be done via a local function with the added benefit of code simplification like:
- parameters of parent method are in scope so the signature of the actual iterator implementation method becomes simpler since there is no need to pass on all arguments.
- type parameters of parent method are in scope so don't need repeating.
- there is one less top-level method to write.
- iterator body appears in-line to the actual public operator method that uses it.
- there is no need for debug-build assertions for arguments that have already been checked
为了回答样式选择 return _(); IEnumerable <T> _()
,我将引用我在 pull request #360 中提供给项目的基本原理:
Putting the return statement and the local function declaration on a single line is designed to compensate for the fact that the language doesn't support anonymous iterators succinctly. There is no extra information or context being provided on that line so there's no clarity gained by separating the two except that it might appear a little unorthodox in styling. Just consider how many things are immaterial:
- The name of the local iterator function so it is given the bogus name of
_
.- The return type of the local function because it's redundant with the outer method's return type.
- The call to the local function never really executes because the iterator function becomes lazy so it's even somewhat misleading to highlight the call on its own.
What's in fact being returned is an iterator object with the body of its algorithm and so the style of putting it all on a single line is designed to make it appear just as that.
The origin and play on the styling come from the idea that…
If you squint hard enough, you can almost believe that C# 7 now has anonymous iterators…
另请参阅 #291 中的一些示例。