Although closures are mainly functional programming, and the main feature of C#is object-oriented, C#can also write code with a flavor of functional programming using delegates or lambda expressions. Similarly, you can use closures in C#using delegates or lambda expressions.
According to WIKI's definition, closures, also known as grammatical closures or functional closures, are a technique for implementing grammatical binding in functional programming languages. A closure is implemented as a structure that stores a function (usually its entry address) and an associated environment (equivalent to a symbolic lookup table). Closures can also delay the lifetime of a variable.
Um... It seems a bit confusing to look at the definition. Let's take a look at the following example
class Program
{
static Action CreateGreeting(string message)
{
return () => { Console.WriteLine("Hello " + message); };
}
static void Main()
{
Action action = CreateGreeting("DeathArthas");
action();
}
}
This example is very simple. Use a lambda expression to create an Action object and then call the Action object.
但是仔细观察会发现,当 Action 对象被调用的时候,CreateGreeting方法已经返回了,作为它的实参的 message 应该已经被销毁了,那么为什么我们在调用 Action 对象的时候,还是能够得到正确的结果呢?
The mystery turns out that the closure is formed here. Although CreateGreeting has been returned, its local variables are captured by the returned lambda expression, delaying its lifetime. How about it? Looking back at the definition of closures this way, is it clearer?
Closures are that simple. In fact, we use them all the time, but sometimes we don't even know it. For example, everyone must have written code similar to the following.
void AddControlClickLogger(Control control, string message)
{
control.Click += delegate
{
Console.WriteLine("Control clicked: {0}", message);
}
}
The code here actually uses closures because we can be sure that when control is clicked, the message has long exceeded its declaration period. Reasonable use of closures ensures that we write delegates that are decoupled in space and time.
However, when using closures, there is a trap to be aware of. Because closures delay the life cycle of local variables, in some cases the program may produce different results than expected. Let's take a look at the following example.
class Program
{
static List<Action> CreateActions()
{
var result = new List<Action>();
for(int i = 0; i < 5; i++)
{
result.Add(() => Console.WriteLine(i));
}
return result;
}
static void Main()
{
var actions = CreateActions();
for(int i = 0;i<actions.Count;i++)
{
actions[i]();
}
}
}
This example is also very simple, create a list of actions and execute them in turn. look at the results
5
5
5
5
5

I believe many people have this expression when they see this result!! Shouldn't it be 0, 1, 2, 3, 4? What's the problem?
To get to the bottom of it, the problem here still lies in the nature of closures. On the other side of the coin "closures delay the life cycle of variables", a variable may be inadvertently referenced by multiple closures.
In this example, the local variable i is referenced by five closures at the same time, and these five closures share i, so the value they print out is the same, which is the value 5 when i finally exited the loop.
To solve this problem, it is easy to declare one more local variable and let each closure refer to its own local variable.
//其他都保持与之前一致
static List<Action> CreateActions()
{
var result = new List<Action>();
for (int i = 0; i < 5; i++)
{
int temp = i; //添加局部变量
result.Add(() => Console.WriteLine(temp));
}
return result;
}
0
1
2
3
4
In this way, each closure references different local variables, and the problem just now is solved.
除此之外,还有一个修复的方法,在创建闭包的时候,使用foreach而不是for。至少在 C# 7.0 的版本上面,这个问题已经被注意到了,使用 foreach 的时候编译器会自动生成代码绕过这个闭包陷阱。
//这样fix也是可以的
static List<Action> CreateActions()
{
var result = new List<Action>();
foreach (var i in Enumerable.Range(0,5))
{
result.Add(() => Console.WriteLine(i));
}
return result;
}
This is a small trap in the use of closures in C#and its use. I hope everyone can understand this knowledge point through Lao Hu's article and avoid detours in development!