wordpress 关闭站点,wordpress分类插件,抖音号出售网站,a链接下载wordpress今天我们来聊一聊 C# 中的本地函数。本地函数是从 C# 7.0 开始引入#xff0c;并在 C# 8.0 和 C# 9.0 中加以完善的。引入本地函数的原因我们来看一下微软 C# 语言首席设计师 Mads Torgersen 的一段话#xff1a;Mads Torgersen#xff1a;我们认为这个场景是有用的 —— 您… 今天我们来聊一聊 C# 中的本地函数。本地函数是从 C# 7.0 开始引入并在 C# 8.0 和 C# 9.0 中加以完善的。引入本地函数的原因我们来看一下微软 C# 语言首席设计师 Mads Torgersen 的一段话Mads Torgersen我们认为这个场景是有用的 —— 您需要一个辅助函数。您仅能在单个函数中使用它并且它可能使用包含在该函数作用域内的变量和类型参数。另一方面与 lambda 不同您不需要将其作为第一类对象因此您不必关心为它提供一个委托类型并分配一个实际的委托对象。另外您可能希望它是递归的或泛型的或者将其作为迭代器实现。[1]正是 Mads Torgersen 所说的这个原因让 C# 语言团队添加了对本地函数的支持。本人在近期的项目中多次用到本地函数发现它比使用委托加 Lambda 表达式的写法更加方便和清晰。本地函数是什么用最简单的大白话来说本地函数就是方法中的方法是不是一下子就理解了不过这样理解本地函数难免有点片面和肤浅。我们来看一下官方对本地函数的定义本地函数是一种嵌套在另一个成员中的私有方法仅能从包含它的成员中调用它。 [2]定义中点出了三个重点本地函数是私有方法。本地函数是嵌套在另一成员中的方法。只能从定义该本地函数的成员中调用它其它位置都不可以。其中可以声明和调用本地函数的成员有以下几种方法尤其是迭代器方法和异步方法构造函数属性访问器事件访问器匿名方法Lambda 表达式析构函数其它本地函数举个简单的示例在方法 M 中定义一个本地函数 addpublic class C
{public void M(){int result add(100, 200);// 本地函数 addint add(int a, int b) { return a b; }}
}
本地函数都是私有的目前可用的修饰符只有 async、unsafe、static静态本地函数无法访问局部变量和实例成员 和 extern 四种。在包含成员中定义的所有本地变量和其方法参数都可在非静态的本地函数中访问。本地函数可以声明在其包含成员中的任意位置但通常的习惯是声明在其包含成员的最后位置即结束 } 之前。本地函数与Lambda表达式的比较本地函数和我们熟知的 Lambda 表达式 [3]非常相似比如上面示例中的本地函数我们可以使用 Lambda 表达式实现如下public void M()
{// Lambda 表达式Funcint, 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)
{Funcuint, uint Fibonacci null; //这里必须明确赋值Fibonacci num {if (num 0) return 0;if (num 1) return 1;return checked(Fibonacci(num - 2) Fibonacci(num - 1));};return Fibonacci(n);
}
命名本地函数的命名方式和类中的方法类似声明本地函数的过程就像是编写普通方法。Lambda 表达式是一种匿名方法需要分配给委托类型的变量通常是 Action 或 Func 类型的变量。参数和返回值类型本地函数因为语法类似于普通方法所以参数类型和返回值类型已经是函数声明的一部分。Lambda 表达式依赖于为其分配的 Action 或 Func 变量的类型来确定参数和返回值的类型。明确赋值本地函数是在编译时定义的方法。由于未将本地函数分配给变量因此可以从包含它的成员的任意代码位置调用它们。在本例中我们将本地函数 Fibonacci 定义在其包含方法 LocFunFibonacci 的 return 语句之后方法体的结束 } 之前而不会有任何编译错误。而 Lambda 表达式是在运行时声明和分配的对象。使用 Lambda 表达式时必须先对其进行明确赋值声明要分配给它的 Action 或 Func 变量并为其分配 Lambda 表达式然后才能在后面的代码中调用它们。在本例中我们首先声明并初始化了一个委托变量 Fibonacci 然后将 Lambda 表达式赋值给了该委托变量。这些区别意味着使用本地函数创建递归算法会更轻松。因为在创建递归算法时使用本地函数和使用普通方法是一样的; 而使用 Lambda 表达式则必须先声明并初始化一个委托变量然后才能将其重新分配给引用相同 Lambda 表达式的主体。变量捕获我们使用 VS 编写或者编译代码时编译器可以对代码执行静态分析提前告知我们代码中存在的问题。看下面一个例子static int M1()
{int num; //这里不用赋值默认值LocalFunction();return num; //OKvoid LocalFunction() num 8; // 本地函数
}static int M2()
{int num; //这里必须赋值默认值比如改为int num 0;下面使用 num 的行才不会报错Action lambdaExp () num 8; // Lambda 表达式lambdaExp();return num; //错误 CS0165 使用了未赋值的局部变量“num”
}
在使用本地函数时因为本地函数是在编译时定义的编译器可以确定在调用本地函数 LocalFunction 时明确分配 num。因为在 return 语句之前调用了 LocalFunction也就在 return 语句前明确分配了 num所以不会引发编译异常。而在使用 Lambda 表达式时因为 Lambda 表达式是在运行时声明和分配的所以在 return 语句前编译器不能确定是否分配了 num所以会引发编译异常。内存分配为了更好地理解本地函数和 Lambda 表达式在分配上的区别我们先来看下面两个例子并看一下它们编译后的代码。Lambda 表达式public class C
{public void M(){int c 300;int d 400;int num c d;//Lambda 表达式Funcint, int, int add (int a, int b) a b c d;var num2 add(100, 200);}
}
使用 Lambda 表达式编译后的代码如下public class C
{[CompilerGenerated]private sealed class c__DisplayClass0_0{public int c;public int d;internal int Mb__0(int a, int b){return a b c d;}}public void M(){c__DisplayClass0_0 c__DisplayClass0_ new c__DisplayClass0_0();c__DisplayClass0_.c 300;c__DisplayClass0_.d 400;int num c__DisplayClass0_.c c__DisplayClass0_.d;Funcint, int, int func new Funcint, int, int(c__DisplayClass0_.Mb__0);int num2 func(100, 200);}
}
可以看出使用 Lambda 表达式时编译后实际上是生成了包含实现方法的一个类然后创建该类的一个对象并将其分配给了委托。因为要创建类的对象所以需要额外的堆heap分配。我们再来看一下具有同样功能的本地函数实现public class C
{public void M(){int c 300;int d 400;int num c d;var num2 add(100, 200);//本地函数int add(int a, int b) { return a b c d; }}
}
使用本地函数编译后的代码如下public class C
{[StructLayout(LayoutKind.Auto)][CompilerGenerated]private struct c__DisplayClass0_0{public int c;public int d;}public void M(){c__DisplayClass0_0 c__DisplayClass0_ default(c__DisplayClass0_0);c__DisplayClass0_.c 300;c__DisplayClass0_.d 400;int num c__DisplayClass0_.c c__DisplayClass0_.d;int num2 Mg__add|0_0(100, 200, ref c__DisplayClass0_);}[CompilerGenerated]private static int Mg__add|0_0(int a, int b, ref c__DisplayClass0_0 P_2){return a b P_2.c P_2.d;}
}
可以看出使用本地函数时编译后只是在包含类中生成了一个私有方法因此调用时不需要实例化对象不需要额外的堆heap分配。当本地函数中使用到其包含成员中的变量时编译器生成了一个结构体并将此结构体的实例以引用ref方式传递到了本地函数这也有助于节省内存分配。综上所述使用本地函数相比使用 Lambda 表达式更能节省时间和空间上的开销。本地函数与异常本地函数还有一个比较实用的功能是可以在迭代器方法和异步方法中立即显示异常。我们知道迭代器方法的主体是延迟执行的所以仅在枚举其返回的序列时才显示异常而并非在调用迭代器方法时。我们来看一个经典的迭代器方法的例子static void Main(string[] args)
{int[] list new[] { 1, 2, 3, 4, 5, 6 };var result Filter(list, null);Console.WriteLine(string.Join(,, result));
}public static IEnumerableT FilterT(IEnumerableT source, FuncT, bool predicate)
{if (source null) throw new ArgumentNullException(nameof(source));if (predicate null) throw new ArgumentNullException(nameof(predicate));foreach (var element in source)if (predicate(element))yield return element;
}
运行上面的代码由于迭代器方法的主体是延迟执行的所以抛出异常的位置将发生在 string.Join(,, result) 所在的行也就是在枚举返回的序列结果 result 时显示如图如果我们把上面的迭代器方法 Filter 中的迭代器部分放入本地函数static void Main(string[] args)
{int[] list new[] { 1, 2, 3, 4, 5, 6 };var result Filter(list, null);Console.WriteLine(string.Join(,, result));
}public static IEnumerableT FilterT(IEnumerableT source, FuncT, bool predicate)
{if (source null) throw new ArgumentNullException(nameof(source));if (predicate null) throw new ArgumentNullException(nameof(predicate));//本地函数IEnumerableT Iterator(){foreach (var element in source)if (predicate(element))yield return element;}return Iterator();
}
那么这时抛出异常的位置将发生在 Filter(list, null) 所在的行也就是在调用 Filter 方法时显示如图可以看出使用了本地函数包装迭代器逻辑的写法相当于把显示异常的位置提前了这有助于我们更快的观察到异常并进行处理。同理在使用了 async 的异步方法中如果把异步执行部分放入 async 的本地函数中也有助于立即显示异常。由于篇幅问题这里不再举例可以查看官方文档。总结综上所述本地函数是方法中的方法但它又不仅仅是方法中的方法它还可以出现在构造函数、属性访问器、事件访问器等等成员中本地函数在功能上类似于 Lambda 表达式但它比 Lambda 表达式更加方便和清晰在分配和性能上也比 Lambda 表达式略占优势本地函数支持范型和作为迭代器实现本地函数还有助于在迭代器方法和异步方法中立即显示异常。相关链接https://github.com/dotnet/roslyn/issues/3911 C# Design Meeting Notes ↩︎https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/classes-and-structs/local-functions 本地函数 ↩︎https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/operators/lambda-expressions Lambda 表达式 ↩︎作者 技术译民 出品 技术译站https://ITTranslator.cn/END