今天我们来聊一聊 C# 中的本地函数。本地函数是从 C# 7.0 开始引入,并在 C# 8.0 和 C# 9.0 中加以完善的。
引入本地函数的原因
我们来看一下微软 C# 语言首席设计师 Mads Torgersen 的一段话:
Mads Torgersen:
我们认为这个场景是有用的 —— 您需要一个辅助函数。 您仅能在单个函数中使用它,并且它可能使用包含在该函数作用域内的变量和类型参数。 另一方面,与 lambda 不同,您不需要将其作为第一类对象,因此您不必关心为它提供一个委托类型并分配一个实际的委托对象。 另外,您可能希望它是递归的或泛型的,或者将其作为迭代器实现。1
正是 Mads Torgersen 所说的这个原因,让 C# 语言团队添加了对本地函数的支持。
本人在近期的项目中多次用到本地函数,发现它比使用委托加 Lambda 表达式的写法更加方便和清晰。
本地函数是什么
用最简单的大白话来说,本地函数就是方法中的方法,是不是一下子就理解了?不过,这样理解本地函数难免有点片面和肤浅。
我们来看一下官方对本地函数的定义:
本地函数是一种嵌套在另一个成员中的私有方法,仅能从包含它的成员中调用它。 2
定义中点出了三个重点:
- 本地函数是私有方法。
- 本地函数是嵌套在另一成员中的方法。
- 只能从定义该本地函数的成员中调用它,其它位置都不可以。
其中,可以声明和调用本地函数的成员有以下几种:
- 方法,尤其是迭代器方法和异步方法
- 构造函数
- 属性访问器
- 事件访问器
- 匿名方法
- Lambda 表达式
- 析构函数
- 其它本地函数
举个简单的示例,在方法 M
中定义一个本地函数 add
:
public class C
{
public void M()
{
int result = add(100, 200);
// 本地函数 add
int add(int a, int b) {
return a + b; }
}
}
本地函数都是私有的,目前可用的修饰符只有 async
、unsafe
、static
(静态本地函数无法访问局部变量和实例成员) 和 extern
四种。在包含成员中定义的所有本地变量和其方法参数都可在非静态的本地函数中访问。本地函数可以声明在其包含成员中的任意位置,但通常的习惯是声明在其包含成员的最后位置(即结束 }
之前)。
本地函数与 Lambda 表达式的比较
本地函数和我们熟知的 Lambda 表达式 3非常相似,比如上面示例中的本地函数,我们可以使用 Lambda 表达式实现如下:
public void M()
{
// Lambda 表达式
Func<int, int, int> add = (int a, int b) => a + b;
int result = add(100, 200);
}
如此看来,似乎选择使用 Lambda 表达式还是本地函数只是编码风格和个人偏好问题。但是,应该注意到,使用它们的时机和条件其实是存在很大差异的。
我们来看一下获取斐波那契数列第 n 项的例子,其实现包含递归调用。
// 使用本地函数的版本
public static uint LocFunFibonacci(uint n)
{
return Fibonacci(n);
uint Fibonacci(uint num)
{
if (num == 0) return 0;
if (num == 1) return 1;
return checked(Fibonacci(num - 2) + Fibonacci(num - 1));
}
}
// 使用 Lambda 表达式的版本
public static uint LambdaFibonacci(uint n)
{
Func<uint, uint> Fibonacci = null; //这里必须明确赋值
Fibonacci = num => {
if (num == 0) return 0