什么是闭包?我们在。net中有吗?

如果它们确实存在于。net中,你能提供一个代码片段(最好是c#)来解释它吗?


当前回答

基本上闭包是一段代码,你可以把它作为参数传递给函数。c#支持匿名委托形式的闭包。

这里有一个简单的例子: 列表。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; });

使用c# 3.0语法,我们可以这样写:

ints.Find(value => value == 1);

其他回答

基本上闭包是一段代码,你可以把它作为参数传递给函数。c#支持匿名委托形式的闭包。

这里有一个简单的例子: 列表。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; });

使用c# 3.0语法,我们可以这样写:

ints.Find(value => value == 1);

下面是我从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; };
}

所以,这里有一些代码,展示了如何使用上面的代码…

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

希望这对大家有所帮助。

闭包是保留原始作用域变量值的函数值。c#可以以匿名委托的形式使用它们。

举一个非常简单的例子,看看下面的c#代码:

delegate int testDel();

static void Main(string[] args)
{
    int foo = 4;
    testDel myClosure = delegate()
    {
        return foo;
    };
    int bar = myClosure();
}

在它的末尾,bar将被设置为4,并且myClosure委托可以传递给程序的其他地方使用。

闭包可以用于很多有用的事情,比如延迟执行或简化接口——LINQ主要是使用闭包构建的。对于大多数开发人员来说,最直接的方法是向动态创建的控件添加事件处理程序——当控件实例化时,您可以使用闭包来添加行为,而不是将数据存储在其他地方。

如果您编写内联匿名方法(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方法作为委托从另一个作用域执行的。