.NET:C# 如何实现的闭包?

时间:2024-09-13 14:05:32

背景

C# 在编译器层面为我们提供了闭包机制(Java7 和 Go 也是这种思路),本文简单的做个解释。

背景知识

你必须了解:引用类型、值类型、引用、对象、值类型的值(简称值)。

关于引用、对象和值在内存的分配有如下几点规则:

  • 对象分配在堆中。
  • 作为字段的引用分配在堆中(内嵌在对象中)。
  • 作为局部变量(参数也是局部变量)的引用分配在栈中。
  • 作为字段的值分配在堆中(内嵌在对象中)。
  • 作为局部变量(参数也是局部变量)的值用分配在栈中。
  • 局部变量只能存活于所在的作用域(方法中的大括号确定了作用域的长短)。

注:按值传递和按引用传递也是需要掌握的知识点,C# 默认是按值传递的。

闭包示例

测试代码

         private static void Before()
{
Action[] actions = new Action[]; for (var i = ; i < actions.Length; i++)
{
actions[i] = () =>
{
Console.WriteLine(i);
};
} foreach (var item in actions)
{
item();
}
}

输出结果

.NET:C# 如何实现的闭包?

编译器帮我们做了是什么?

编译器帮我们生成的代码(我自己写的,可以使用 Reflector 工具自己查看)

         private static void After()
{
Action[] actions = new Action[]; var anonymous = new AnonymousClass(); for (anonymous.i = ; anonymous.i < actions.Length; anonymous.i++)
{
actions[anonymous.i ] = anonymous.Action;
} foreach (var item in actions)
{
item();
}
} class AnonymousClass
{
public int i; public void Action()
{
Console.WriteLine(this.i);
}
}

如何修复上面的问题?

上面的例子不是我们期望的输出,让我们给出两种修改方案:

第一种(借鉴JS)

         private static void Fix()
{
Action[] actions = new Action[]; for (var i = ; i < actions.Length; i++)
{
new Action<int>((j) =>
{
actions[i] = () =>
{
Console.WriteLine(j);
};
})(i);
} foreach (var item in actions)
{
item();
}
}

第二种

         public static void Fix2()
{
Action[] actions = new Action[]; for (var i = ; i < actions.Length; i++)
{
var j = i; actions[i] = () =>
{
Console.WriteLine(j);
};
} foreach (var item in actions)
{
item();
}
}

分析

编译器将闭包引用的局部变量转换为匿名类型的字段,导致了局部变量分配在堆中。

备注

C# 编译器帮我们做了非常多的工作,如:自动属性、类型推断、匿名类型、匿名委托、Lamda 表达式、析构方法、await 和 sync、using、对象初始化表达式、lock、默认参数 等等,这些统称为“语法糖”。

相关文章