匿名方法

目录

匿名方法定义
匿名方法的声明
匿名方法的使用
范围捕获局部变量或实例状态
匿名函数会捕获变量,并延长对象的生命周期
结束匿名方法捕获局部变量-静态匿名函数

 

匿名方法定义:

匿名方法是没有名称只有主体的方法。 在匿名方法中您不需要指定返回类型,它是从方法主体内的 return 语句推断的。 匿名方法是通过使用 delegate 关键字创建委托实例来声明的。创建匿名方法实际上是一种将代码块作为委托参数传递的方式,使用这一种方法如果建立新的线程可以减少内存的消耗,匿名方法不声明返回值类型,但是匿名方法的返回值类型必须和委托返回值一样。

匿名方法的声明

第一种方式:

delegate (int a, int b) { return a + b; };// 有参数的声明方式
delegate(){ System.Console.Write("Hello, "); System.Console.WriteLine("World!"); };//没参数声明方式

第二种方式:可以将创建的匿名方法转换为具有任何参数列表的委托类型。这是 lambda 表达式不支持的匿名方法的唯一功能。 在所有其他情况下,lambda 表达式是编写内联代码的首选方法。

delegate { Console.WriteLine("Hello!"); };

匿名方法的使用

 

第一种方法使用:
Func<int, int, int> sum = delegate (int a, int b) { return a + b; };//匿名方法的返回值类型必须和委托返回值一样
Console.WriteLine(sum(
3, 4)); // output: 7 第二种方法的使用: Action greet = delegate { Console.WriteLine("Hello!"); };//这种匿名方法可以转换为具有任何参数列表的委托类型 greet(); Action<int, double> introduce = delegate { Console.WriteLine("This is world!"); }; introduce(42, 2.7);

从 C# 9.0 开始,可以使用弃元指定该方法不使用的两个或更多个匿名方法输入参数:

 
Func<int, int, int> constant = delegate (int _, int _) { return 42; };
Console.WriteLine(constant(3, 4));  // output: 42

为实现向后兼容性,如果只有一个参数名为 _,则将 _ 视为匿名方法中该参数的名称。

从 C# 9.0 开始,可以在匿名方法的声明中使用 static 修饰符:

Func<int, int, int> sum = static delegate (int a, int b) { return a + b; };
Console.WriteLine(sum(10, 4));  // output: 14

静态匿名方法无法从封闭范围捕获局部变量或实例状态。

 

匿名函数闭包会捕获外层函数的变量地址,并延长对象的生命周期

 总结:

匿名函数会捕获当前上下文的局部变量,捕获是引用地址不是赋值,延长对象的生命周期; 捕获的变量将不会被作为垃圾回收,直到此委托或表达式树被回收掉。

这边说的当前上下文就是private public interal protected声明的那些指函数、变量等多作用域范围

也就是说,只要某个方法中存在没有被回收的匿名函数/lambda 表达式/表达式树,那么当前上下文的对象直到这些匿名函数被回收之前都不会被回收,即便已经设为了 null。

案例:

测试代码是这样的:

private void OnLoaded(object sender, RoutedEventArgs e)
{
    var variable = new MainPage();
    var reference = new WeakReference<MainPage>(variable);
    variable = null;
    GC.Collect();
    Console.WriteLine($"{reference.TryGetTarget(out var target)}: {target}");
    DoSomething(x => DoAnotherThing(x));
}

 

需要验证的是 MainPage 对象是否被回收。然而在这段代码中,MainPage 并没有被回收;然而去掉最后一行,MainPage 便可以正常回收。关键是,即便是在 Console.WriteLine 上打下断点,让代码永远不会执行到最后一句,也不会改变回收的结果。

由于 DoSomething 中的委托参数恰好就是 MainPage 类型的,不禁让人觉得可能是此函数做了一些奇怪的事情。然而毕竟参数中传入的委托参数只是形参,理论上不应该影响到外部对象的回收。那么影响的只可能是变量的捕获了。

于是,我们将最后一行换成别的函数别的参数:

DoSomething(null);

或者将整个这一句提取成新的函数:

private void OnLoaded(object sender, RoutedEventArgs e)
{
    // 省略前面的代码。
    ExtractedMethod();
}

private void ExtractedMethod()
{
    DoSomething(x => DoAnotherThing(x));
}

那么,回收就会正常进行。

现在,不执行这个受争议的函数了,我们使用空的匿名函数。

private void OnLoaded(object sender, RoutedEventArgs e)
{
    var variable = new MainPage();
    var reference = new WeakReference<MainPage>(variable);
    variable = null;
    GC.Collect();
    Console.WriteLine($"{reference.TryGetTarget(out var target)}: {target}");
    Dispatcher.InvokeAsync(() => { });
}

一样会导致不回收。

结论

在微软官方的《C# 规范 5.0》(点此下载)的第 7.15.5.1 章节中有说到:

When an outer variable is referenced by an anonymous function, the outer variable is said to have been captured by the anonymous function. Ordinarily, the lifetime of a local variable is limited to execution of the block or statement with which it is associated (§5.1.7). However, the lifetime of a captured outer variable is extended at least until the delegate or expression tree created from the anonymous function becomes eligible for garbage collection.

匿名函数会捕获当前上下文的局部变量,延长对象的生命周期;直到此委托或表达式树被回收掉。

也就是说,只要某个方法中存在没有被回收的匿名函数/lambda 表达式/表达式树,那么当前上下文的对象直到这些匿名函数被回收之前都不会被回收,即便已经设为了 null。

 

静态匿名函数-结束匿名方法捕获局部变量

您可以在lambda或匿名方法上利用static修饰符,以确保您不会无意中从封闭的上下文中捕获本地或实例状态。这对于提高应用程序的性能非常友好。

从 C# 9.0 开始,可以在匿名方法的声明中使用 static 修饰符:

Func<int, int, int> sum = static delegate (int a, int b) { return a + b; };
Console.WriteLine(sum(10, 4));  // output: 14

 

 

posted @ 2021-09-28 19:20  小林野夫  阅读(826)  评论(0编辑  收藏  举报
原文链接:https://www.cnblogs.com/cdaniu/