当调用将lambda表达式作为参数的方法(例如Enumerable.Where)而不实际声明表达式中的变量或方法参数时,它叫什么?
例如,我熟悉这个 lambda 表达式语法:
public string GetDigits(string input)
{
return new String(input.Where(i => Char.IsDigit(i)).ToArray());
}
但是,我惊讶地发现这也可以写成:
public string GetDigits(string input)
{
return new String(input.Where(Char.IsDigit).ToArray());
}
在第二个代码片段中发生了什么,其中 Char.IsDigit() 方法(显然)是用隐式参数调用的? 这种语法叫什么?
方法不接受 lambda 作为参数。 它们接受委托作为参数。 lambda 只是创建委托的一种方式。
另一种方法是提供方法组,如第二个示例中所做的那样,该方法组可以转换为委托。
类似的方法是使用匿名方法功能。 不过,当它们被添加时,这或多或少被 lambda 取代了,所以你看不到太多。 使用该语法的示例是:
Func<char, bool> predicate = delegate(char c) { return Char.IsDigit(c); };
另一种方法是使用 Delegate.CreateDelegate
创建委托。 (不过,这不是你经常看到的。
最后一种方法是拥有一个从其他地方获得的委托变量。 (在其他地方会使用这些其他选项之一创建委托。
在第二个代码片段中发生了什么,其中 Char.IsDigit() 方法(显然)是用隐式参数调用的?这种语法叫什么?
它没有被召唤。 这就是重点。 我们正在尝试创建一个委托。 委托是跟踪要调用的方法的对象,也是应在其上调用该方法的对象。 然后,您可以调用委托,它将调用用于创建它的方法。 所以这里你不是在调用IsDigit
,而是创建一个指向IsDigit
方法的委托,每当调用该委托时都会调用它。
当您使用 lambda 时,您正在创建一个新方法,可能是在新类中,(两者都没有您可以引用的名称,但它们在运行时会有一个名称),并且该匿名方法的主体将调用 IsDigit
。 然后,lambda 解析为指向该匿名方法的委托,该委托维护了另一个示例的语义,该方法在调用时调用匿名方法,该方法在其实现中调用 IsDigit
。 它增加了一个额外的间接层(可能会也可能不会在运行时得到优化)来完成同样的事情。
Enumerable.where 的签名:
public static IEnumerable<TSource> Where<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate
)
这:
input.Where(i => Char.IsDigit(i))
相当于写作:
Func<char, bool> temp = i => Char.IsDigit(i);
input.Where(temp);
因此,它创建了一个匿名函数,其中包含调用Char.IsDigit
的参数i
。
这:
input.Where(Char.IsDigit)
相当于
Func<char, bool> temp = Char.IsDigit;
input.Where(temp);
这相当于:
Func<char, bool> temp = new Func<char, bool>(Char.IsDigit);
input.Where(temp);
因此,它创建了一个要Char.IsDigit
的委托,然后将其传递给input.Where
。
所以第二个删除了"中间人"(匿名函数)。在这种特殊情况下,它是"合法的",因为匿名函数的i
参数"按原样"传递给Char.IsDigit
。如果是以下情况,情况会有所不同:
input.Where(i => !Char.IsDigit(i))
在这种情况下,您无法删除中间人(匿名函数)。
所有这些都没有名称(或者您可以将第一个称为"创建委托并将其传递给匿名函数"和第二个"创建和传递从方法组创建的委托"......但它们不是漂亮的标语,它们更像是对你正在做的事情的描述)
如果编译器找到与预期签名匹配的单个方法,它将隐式将方法组强制转换为委托,在本例中,委托将单个char
作为输入并返回bool
。
您的Where
需要一个Func<char, bool>
,它是接受char
参数并返回bool
的方法的委托。与此委托匹配的任何内容都是此Where
的有效参数。
- 您编写的 lambda 最初通过类型推断匹配此委托:编译器期望
i
是char
的,基于可枚举源的泛型参数 - 并将返回类型推断为bool
,因为这就是 lambda 中的方法调用表达式将返回的内容。 Char.IsDigit
方法本身也与此匹配。因此,引用该方法是表达相同事物的另一种有效方式。这称为方法组。
如果您考虑到对于每个 lambda 表达式,编译器都会生成一个匿名方法,然后将该匿名方法传递到预期委托的位置,那么这两个可能的Where
参数的语义等效性也是有意义的。
为了说明这一点,请考虑您的原始代码段:
Where(i => Char.IsDigit(i))
编译器将上述内容降低到:
bool AnAnonymousMethod(char i)
{
return Char.IsDigit(i);
}
然后:
Where(AnAnonymousMethod)
如您所见,lambda 语法(在没有捕获变量的情况下,如这里)只是语法糖,用于编写匿名方法,然后在需要兼容委托的情况下使用此新编写的方法的方法组作为参数。