C#中匿名方法变量共享原理分析
2011-10-18 17:24 AnyKoro 阅读(417) 评论(0) 编辑 收藏 举报先上几篇我分析中用到例子的文章
进入正题,正向老赵说的那样,c#本来没有什么匿名方法之说,只是编译器施展的魔法。那么我们首先先看看老赵文中提出的,魔法施展的方法。
“编译器将匿名方法中需要访问的所有成员一起包含在闭包中,确保所有的成员调用都符合.NET标准。”
它的处理方式可以用老赵“友好化”处理的类来说明。
源代码:
class TestClass { private void Print(string message) { Console.WriteLine(message); } public void Test() { string[] messages = new string[] { "Hello", "World" }; int index = 0; Action<string> action = (m) => { this.Print((index++) + ". " + m); }; Array.ForEach(messages, action); Console.WriteLine("index = " + index); } }
编译器处理后的代码:
class TestClass { ... private sealed class AutoGeneratedHelperClass { public TestClass m_testClassInstance; public int m_index; public void Action(string m) { this.m_index++; this.m_testClassInstance.Print(m); } } public void TestAfterCompiled() { AutoGeneratedHelperClass helper = new AutoGeneratedHelperClass(); helper.m_testClassInstance = this; helper.m_index = 0; string[] messages = new string[] { "Hello", "World" }; Action<string> action = new Action<string>(helper.Action); Array.ForEach(messages, action); Console.WriteLine(helper.m_index); } }
由此就可以看出编译器是如何实现一个闭包的:
-
编译器自动生成一个私有的内部辅助类,并将其设为sealed,这个类的实例将成为一个闭包对象。
- 如果匿名方法需要访问方法的参数或局部变量,那么该参数或局部变量将“升级”成为辅助类中的公有Field字段。
- 如果匿名方法需要访问类中的其它方法,那么辅助类中将保存类的当前实例。
这三点感觉说得不够详细,我在这里做更多的解释
AutoGeneratedHelperClass是形成匿名方法的关键。根据匿名方法的特点,函数块中使用到了局部变量。这句话可能比较难懂,我们可以看这段代码
class Program
{
static void Main(string[] args)
{
List<Action> ls = new List<Action>();
for (int i = 0; i < 10; i++)
{
ls.Add(() => Console.WriteLine(i));
}
foreach (Action action in ls)
{
action();
}
System.Console.Read();
}
}
在这里你可以发现,在ls.Add(() => Console.WriteLine(i));这句代码中,使用到了局部变量i。(注意是在函数体内使用到局部变量,而不是作为参数传入。)
有了这个基础后,我们再看之前的,在TestAfterCompiled()中是如何调用这个功能的。我们发现在主要用到了AutoGeneratedHelperClass类,在该方法中,实例化了一个AutoGeneratedHelperClass类,并且通过给该实例对象的字段赋值的手段,来使得可以正常的运行功能。所以,简单理解,在.net中匿名方法的实现,其实就是把匿名方法用到的局部变量封装在一个类(称为N类)中,同时该类还有一个通匿名方法具有相同实现的方法。以此解决了局部变量声明周期的问题。同样的对于匿名方法调用类(使用匿名方法的类,简称O类)方法,也就是在N类中加入一个O类的实例。
<>c__DisplayClass2类就是我们说的N类,然后把注意力放到高亮部分,这个class2就是对应老赵的helper类。由于i赋值的阶段在for之前,所以该类的生成就在循环之前。这样以来就要出问题了。尽管你Add进去的item的形式是对的,但是其所用到得N类却是同一个(类是引用类型),当执行完循环后class2还是class2只是其中的i变了。既然10个action指向同一个N类,那么他们必然打印出10个相同的结果。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
2009-10-18 sql server 2008 error 233 问题解决方法