什么是闭包?我们在。net中有吗?
如果它们确实存在于。net中,你能提供一个代码片段(最好是c#)来解释它吗?
什么是闭包?我们在。net中有吗?
如果它们确实存在于。net中,你能提供一个代码片段(最好是c#)来解释它吗?
当前回答
闭包是在另一个函数(或方法)中定义的函数,它使用父方法中的变量。这种位于方法中并包装在其中定义的函数中的变量的使用称为闭包。
Mark Seemann在他的博客文章中有一些关于闭包的有趣例子,他将面向对象编程和函数式编程进行了比较。
让它更详细
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.
其他回答
如果你有兴趣了解c#如何实现闭包,请阅读“I know the answer (its 42) blog”。
编译器在后台生成一个类来封装匿名方法和变量j
[CompilerGenerated]
private sealed class <>c__DisplayClass2
{
public <>c__DisplayClass2();
public void <fillFunc>b__0()
{
Console.Write("{0} ", this.j);
}
public int j;
}
对于函数:
static void fillFunc(int count) {
for (int i = 0; i < count; i++)
{
int j = i;
funcArr[i] = delegate()
{
Console.Write("{0} ", j);
};
}
}
把它变成:
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);
}
}
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...
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
闭包是传递到创建闭包的函数外部的匿名函数。 它维护它所使用的函数中的任何变量。
如果您编写内联匿名方法(c# 2)或(最好)Lambda表达式(c# 3+),则实际的方法仍在创建中。如果代码使用了一个外部作用域的局部变量—您仍然需要以某种方式将该变量传递给方法。
例如,使用这个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;
});
如果你想在那个lambda表达式中使用I,你必须把它传递给那个创建的方法。
因此出现的第一个问题是:应该通过值还是引用来传递?
通过引用传递(我猜)更可取,因为你可以读/写访问该变量(这就是c#所做的;我猜微软的团队权衡了利弊后,选择了参照;根据Jon Skeet的文章,Java选择了按值)。
但另一个问题出现了:在哪里分配这个i?
它应该实际/自然地分配到堆栈上吗? 如果你在堆栈上分配它并通过引用传递它,可能会出现它比它自己的堆栈框架更长寿的情况。举个例子:
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;
});
}
lambda表达式(在Where子句中)再次创建了一个引用i的方法。如果i分配在Outlive的堆栈上,那么当你枚举whereItems时,生成的方法中使用的i将指向Outlive的i,即指向堆栈中不再可访问的位置。
所以我们需要把它放到堆上。
因此,c#编译器为了支持这个内联匿名/lambda,使用了所谓的“闭包”:它在堆上创建了一个名为DisplayClass的类(相当糟糕),其中有一个包含i的字段,以及实际使用它的函数。
与此等价的东西(你可以看到使用ILSpy或ILDASM生成的IL):
class <>c_DisplayClass1
{
public int i;
public bool <GetFunc>b__0()
{
this.i++;
Console.WriteLine(i);
return true;
}
}
它在局部作用域中实例化该类,并用闭包实例替换与i或lambda表达式相关的任何代码。所以-任何时候你在定义i的“局部作用域”代码中使用i,你实际上是在使用DisplayClass实例字段。
所以如果我要在主方法中改变“本地”I,它实际上会改变_DisplayClass。我;
即。
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
它将打印出12,因为“i = 10”到dispalyclass字段,并在第2个枚举之前更改它。
关于这个主题的一个很好的来源是Bart De Smet Pluralsight模块(需要注册)(也忽略他错误地使用术语“提升”-什么(我认为)他的意思是局部变量(即I)被更改为参考新的DisplayClass字段)。
在其他新闻中,似乎有一些误解,认为“闭包”与循环有关-正如我所理解的,“闭包”不是与循环有关的概念,而是与使用局部作用域变量的匿名方法/ lambda表达式有关-尽管一些技巧问题使用循环来演示它。
闭包是在函数中定义的函数,可以访问它的局部变量以及它的父变量。
public string GetByName(string name)
{
List<things> theThings = new List<things>();
return theThings.Find<things>(t => t.Name == name)[0];
}
find方法中的函数。
t => t.Name == name
可以访问其作用域中的变量t,以及父作用域中的变量名。即使它是由find方法作为委托从另一个作用域执行的。