.NET 中的“闭包”是什么?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/428617/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What are 'closures' in .NET?
提问by Developer
What is a closure? Do we have them in .NET?
什么是闭包?我们在 .NET 中有它们吗?
If they do exist in .NET, could you please provide a code snippet (preferably in C#) explaining it?
如果它们确实存在于 .NET 中,您能否提供一个解释它的代码片段(最好是 C#)?
回答by Jon Skeet
I have an article on this very topic. (It has lots of examples.)
我有一篇关于这个主题的文章。(它有很多例子。)
In essence, a closure is a block of code which can be executed at a later time, but which maintains the environment in which it was first created - i.e. it can still use the local variables etc of the method which created it, even after that method has finished executing.
本质上,闭包是一个代码块,可以在以后执行,但是它保持了它第一次创建的环境——也就是说,它仍然可以使用创建它的方法的局部变量等,即使在那之后方法执行完毕。
The general feature of closures is implemented in C# by anonymous methods and lambda expressions.
闭包的一般特性是通过匿名方法和 lambda 表达式在 C# 中实现的。
Here's an example using an anonymous method:
这是使用匿名方法的示例:
using System;
class Test
{
static void Main()
{
Action action = CreateAction();
action();
action();
}
static Action CreateAction()
{
int counter = 0;
return delegate
{
// Yes, it could be done in one statement;
// but it is clearer like this.
counter++;
Console.WriteLine("counter={0}", counter);
};
}
}
Output:
输出:
counter=1
counter=2
Here we can see that the action returned by CreateAction still has access to the counter variable, and can indeed increment it, even though CreateAction itself has finished.
在这里我们可以看到 CreateAction 返回的 action 仍然可以访问 counter 变量,并且确实可以增加它,即使 CreateAction 本身已经完成。
回答by Developer
If you are interested in seeing how C# implements Closure read "I know the answer (its 42) blog"
如果您有兴趣了解 C# 如何实现 Closure,请阅读“我知道答案(其 42 个)博客”
The compiler generates a class in the background to encapsulate the anoymous method and the variable j
编译器在后台生成一个类来封装anymous方法和变量j
[CompilerGenerated]
private sealed class <>c__DisplayClass2
{
public <>c__DisplayClass2();
public void <fillFunc>b__0()
{
Console.Write("{0} ", this.j);
}
public int j;
}
for the function:
对于功能:
static void fillFunc(int count) {
for (int i = 0; i < count; i++)
{
int j = i;
funcArr[i] = delegate()
{
Console.Write("{0} ", j);
};
}
}
Turning it into:
把它变成:
private static void fillFunc(int count)
{
for (int i = 0; i < count; i++)
{
Program.<>c__DisplayClass1 class1 = new Program.<>c__DisplayClass1();
class1.j = i;
Program.funcArr[i] = new Func(class1.<fillFunc>b__0);
}
}
回答by Dan Monego
Closures are functional values that hold onto variable values from their original scope. C# can use them in the form of anonymous delegates.
闭包是保留其原始作用域中的变量值的函数值。C# 可以以匿名委托的形式使用它们。
For a very simple example, take this C# code:
对于一个非常简单的示例,请使用以下 C# 代码:
delegate int testDel();
static void Main(string[] args)
{
int foo = 4;
testDel myClosure = delegate()
{
return foo;
};
int bar = myClosure();
}
At the end of it, bar will be set to 4, and the myClosure delegate can be passed around to be used elsewhere in the program.
最后,bar 将设置为 4,并且可以传递 myClosure 委托以在程序的其他地方使用。
Closures can be used for a lot of useful things, like delayed execution or to simplify interfaces - LINQ is mainly built using closures. The most immediate way it comes in handy for most developers is adding event handlers to dynamically created controls - you can use closures to add behavior when the control is instantiated, rather than storing data elsewhere.
闭包可用于许多有用的事情,例如延迟执行或简化接口 - LINQ 主要使用闭包构建。对于大多数开发人员来说,最直接的方法是向动态创建的控件添加事件处理程序——当控件被实例化时,您可以使用闭包来添加行为,而不是将数据存储在其他地方。
回答by AnthonyWJones
Func<int, int> GetMultiplier(int a)
{
return delegate(int b) { return a * b; } ;
}
//...
var fn2 = GetMultiplier(2);
var fn3 = GetMultiplier(3);
Console.WriteLine(fn2(2)); //outputs 4
Console.WriteLine(fn2(3)); //outputs 6
Console.WriteLine(fn3(2)); //outputs 6
Console.WriteLine(fn3(3)); //outputs 9
A closure is an anonymous function passed outside of the function in which it is created. It maintains any variables from the function in which it is created that it uses.
闭包是在创建它的函数之外传递的匿名函数。它维护它所使用的创建它的函数中的任何变量。
回答by Jason Bunting
Here is a contrived example for C# which I created from similar code in JavaScript:
这是我从 JavaScript 中的类似代码创建的 C# 的人为示例:
public delegate T Iterator<T>() where T : class;
public Iterator<T> CreateIterator<T>(IList<T> x) where T : class
{
var i = 0;
return delegate { return (i < x.Count) ? x[i++] : null; };
}
So, here is some code that shows how to use the above code...
所以,这里有一些代码展示了如何使用上面的代码......
var iterator = CreateIterator(new string[3] { "Foo", "Bar", "Baz"});
// So, although CreateIterator() has been called and returned, the variable
// "i" within CreateIterator() will live on because of a closure created
// within that method, so that every time the anonymous delegate returned
// from it is called (by calling iterator()) it's value will increment.
string currentString;
currentString = iterator(); // currentString is now "Foo"
currentString = iterator(); // currentString is now "Bar"
currentString = iterator(); // currentString is now "Baz"
currentString = iterator(); // currentString is now null
Hope that is somewhat helpful.
希望这有点帮助。
回答by aku
Basically closure is a block of code that you can pass as an argument to a function. C# supports closures in form of anonymous delegates.
基本上,闭包是一段代码,您可以将其作为参数传递给函数。C# 支持匿名委托形式的闭包。
Here is a simple example:
List.Find method can accept and execute piece of code (closure) to find list's item.
这是一个简单的例子:
List.Find 方法可以接受并执行一段代码(闭包)来查找列表的项目。
// Passing a block of code as a function argument
List<int> ints = new List<int> {1, 2, 3};
ints.Find(delegate(int value) { return value == 1; });
Using C#3.0 syntax we can write this as:
使用 C#3.0 语法,我们可以将其写为:
ints.Find(value => value == 1);
回答by meJustAndrew
A closure is when a function is defined inside another function (or method) and it uses the variables from the parent method. This use of variables which are located in a method and wrapped in a function defined within it, is called a closure.
闭包是在另一个函数(或方法)中定义一个函数,并且它使用来自父方法的变量。这种位于方法中并包装在其中定义的函数中的变量的使用称为闭包。
Mark Seemann has some interesting examples of closures in his blog postwhere he does a paralel between oop and functional programming.
Mark Seemann 在他的博客文章中提供了一些有趣的闭包示例,其中他在 oop 和函数式编程之间进行了并行处理。
And to make it more detailed
并使其更详细
var workingDirectory = new DirectoryInfo(Environment.CurrentDirectory);//when this variable
Func<int, string> read = id =>
{
var path = Path.Combine(workingDirectory.FullName, id + ".txt");//is used inside this function
return File.ReadAllText(path);
};//the entire process is called a closure.
回答by Charles Bretana
Closures are chunks of code that reference a variable outside themselves, (from below them on the stack), that might be called or executed later, (like when an event or delegate is defined, and could get called at some indefinite future point in time)... Because the outside variable that the chunk of code references may gone out of scope (and would otherwise have been lost), the fact that it is referenced by the chunk of code (called a closure) tells the runtime to "hold" that variable in scope until it is no longer needed by the closure chunk of code...
闭包是引用外部变量的代码块(从它们下面的堆栈中),以后可能会被调用或执行,(例如定义事件或委托时,可能会在某个不确定的未来时间点被调用)... 因为代码块引用的外部变量可能超出范围(否则会丢失),它被代码块(称为闭包)引用的事实告诉运行时“保持“该变量在范围内,直到闭包代码块不再需要它为止......
回答by Hameed Syed
Just out of the blue,a simple and more understanding answer from the book C# 7.0 nutshell.
突然之间,来自 C# 7.0 nutshell 一书中的一个简单且更容易理解的答案。
Pre-requisit you should know:A lambda expression can reference the local variables and parameters of the method in which it's defined (outer variables).
先决条件你应该知道:一个 lambda 表达式可以引用定义它的方法的局部变量和参数(外部变量)。
static void Main()
{
int factor = 2;
//Here factor is the variable that takes part in lambda expression.
Func<int, int> multiplier = n => n * factor;
Console.WriteLine (multiplier (3)); // 6
}
Real part:Outer variables referenced by a lambda expression are called captured variables. A lambda expression that captures variables is called a closure.
实部:由 lambda 表达式引用的外部变量称为捕获变量。捕获变量的 lambda 表达式称为闭包。
Last Point to be noted:Captured variables are evaluated when the delegate is actually invoked, not when the variables were captured:
最后一点要注意:在实际调用委托时评估捕获的变量,而不是在捕获变量时:
int factor = 2;
Func<int, int> multiplier = n => n * factor;
factor = 10;
Console.WriteLine (multiplier (3)); // 30
回答by David Refaeli
If you write an inline anonymous method (C#2) or (preferably) a Lambda expression (C#3+), an actual method is still being created. If that code is using an outer-scope local variable - you still need to pass that variable to the method somehow.
如果您编写内联匿名方法 (C#2) 或(最好)Lambda 表达式 (C#3+),则仍在创建实际方法。如果该代码使用外部作用域局部变量 - 您仍然需要以某种方式将该变量传递给该方法。
e.g. take this Linq Where clause (which is a simple extension method which passes a lambda expression):
例如,使用这个 Linq Where 子句(这是一个传递 lambda 表达式的简单扩展方法):
var i = 0;
var items = new List<string>
{
"Hello","World"
};
var filtered = items.Where(x =>
// this is a predicate, i.e. a Func<T, bool> written as a lambda expression
// which is still a method actually being created for you in compile time
{
i++;
return true;
});
if you want to use i in that lambda expression, you have to pass it to that created method.
如果你想在那个 lambda 表达式中使用 i,你必须将它传递给那个 created 方法。
So the first question that arises is: should it be passed by value or reference?
所以出现的第一个问题是:应该按值传递还是按引用传递?
Pass by reference is (I guess) more preferable as you get read/write access to that variable (and this is what C# does; I guess the team in Microsoft weighed the pros and cons and went with by-reference; According to Jon Skeet's article, Java went with by-value).
当您获得对该变量的读/写访问权限时,通过引用传递(我猜)更可取(这就是 C# 所做的;我猜微软的团队权衡了利弊并采用了引用;根据Jon Skeet 的说法文章中,Java 使用了按值)。
But then another question arises: Where to allocate that i?
但随后出现了另一个问题:在哪里分配 i?
Should it actually/naturally be allocated on the stack? Well, if you allocate it on the stack and pass it by reference, there can be situations where it outlives it's own stack frame. Take this example:
它应该实际上/自然地分配在堆栈上吗?好吧,如果你在堆栈上分配它并通过引用传递它,可能会出现它比它自己的堆栈帧更长寿的情况。拿这个例子:
static void Main(string[] args)
{
Outlive();
var list = whereItems.ToList();
Console.ReadLine();
}
static IEnumerable<string> whereItems;
static void Outlive()
{
var i = 0;
var items = new List<string>
{
"Hello","World"
};
whereItems = items.Where(x =>
{
i++;
Console.WriteLine(i);
return true;
});
}
The lambda expression (in the Where clause) again creates a method which refers to an i. If i is allocated on the stack of Outlive, then by the time you enumerate the whereItems, the i used in the generated method will point to the i of Outlive, i.e. to a place in the stack that is no longer accessible.
lambda 表达式(在 Where 子句中)再次创建了一个引用 i 的方法。如果 i 分配在 Outlive 的堆栈上,那么当您枚举 whereItems 时,生成的方法中使用的 i 将指向 Outlive 的 i,即指向堆栈中不再可访问的位置。
Ok, so we need it on the heap then.
好的,所以我们需要它在堆上。
So what the C# compiler does to support this inline anonymous/lambda, is use what is called "Closures": It creates a class on the Heap called (rather poorly) DisplayClass which has a field containing the i, and the Function that actually uses it.
因此,C# 编译器为支持此内联匿名/lambda 所做的是使用所谓的“闭包”:它在堆上创建一个名为(相当糟糕)DisplayClass的类,该类具有一个包含 i 的字段,以及实际使用的函数它。
Something that would be equivalent to this (you can see the IL generated using ILSpy or ILDASM):
与此等效的东西(您可以看到使用 ILSpy 或 ILDASM 生成的 IL):
class <>c_DisplayClass1
{
public int i;
public bool <GetFunc>b__0()
{
this.i++;
Console.WriteLine(i);
return true;
}
}
It instantiates that class in your local scope, and replaces any code relating to i or the lambda expression with that closure instance. So - anytime you are using the i in your "local scope" code where i was defined, you are actually using that DisplayClass instance field.
它在您的本地范围内实例化该类,并用该闭包实例替换与 i 或 lambda 表达式相关的任何代码。因此 - 任何时候您在定义 i 的“本地范围”代码中使用 i 时,您实际上都在使用该 DisplayClass 实例字段。
So if I would change the "local" i in the main method, it will actually change _DisplayClass.i ;
因此,如果我要更改 main 方法中的“本地” i ,它实际上会更改 _DisplayClass.i ;
i.e.
IE
var i = 0;
var items = new List<string>
{
"Hello","World"
};
var filtered = items.Where(x =>
{
i++;
return true;
});
filtered.ToList(); // will enumerate filtered, i = 2
i = 10; // i will be overwriten with 10
filtered.ToList(); // will enumerate filtered again, i = 12
Console.WriteLine(i); // should print out 12
it will print out 12, as "i = 10" goes to that dispalyclass field and changes it just before the 2nd enumeration.
它将打印出 12,因为“i = 10”转到该 dispalyclass 字段并在第二次枚举之前更改它。
A good source on the topic is this Bart De Smet Pluralsight module(requires registration) (also ignore his erroneous use of the term "Hoisting" - what (I think) he means is that the local variable (i.e. i) is changed to refer to the the new DisplayClass field).
这个主题的一个很好的来源是这个Bart De Smet Pluralsight 模块(需要注册)(也忽略他对术语“提升”的错误使用 - (我认为)他的意思是局部变量(即 i)被更改为引用到新的 DisplayClass 字段)。
In other news, there seems to be some misconception that "Closures" are related to loops - as I understand "Closures" are NOT a concept related to loops, but rather to anonymous methods / lambda expressions use of local scoped variables - although some trick questions use loops to demonstrate it.
在其他新闻中,似乎存在一些误解,认为“闭包”与循环有关——据我所知,“闭包”不是与循环相关的概念,而是与匿名方法/使用局部范围变量的 lambda 表达式相关的概念——尽管有一些技巧问题使用循环来演示它。

