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

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


当前回答

如果您编写内联匿名方法(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表达式有关-尽管一些技巧问题使用循环来演示它。

其他回答

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...

闭包是保留原始作用域变量值的函数值。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表达式有关-尽管一些技巧问题使用循环来演示它。

A closure aims to simplify functional thinking, and it allows the runtime to manage state, releasing extra complexity for the developer. A closure is a first-class function with free variables that are bound in the lexical environment. Behind these buzzwords hides a simple concept: closures are a more convenient way to give functions access to local state and to pass data into background operations. They are special functions that carry an implicit binding to all the nonlocal variables (also called free variables or up-values) referenced. Moreover, a closure allows a function to access one or more nonlocal variables even when invoked outside its immediate lexical scope, and the body of this special function can transport these free variables as a single entity, defined in its enclosing scope. More importantly, a closure encapsulates behavior and passes it around like any other object, granting access to the context in which the closure was created, reading, and updating these values.

我有一篇关于这个主题的文章。(书中有很多例子。)

从本质上讲,闭包是一段可以在以后执行的代码,但它维护了它第一次创建时的环境——即它仍然可以使用创建它的方法的局部变量等,即使该方法已经执行完毕。

闭包的一般特性是通过匿名方法和lambda表达式在c#中实现的。

下面是一个使用匿名方法的例子:

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);
        };
    }
}

输出:

counter=1
counter=2

在这里,我们可以看到CreateAction返回的操作仍然可以访问counter变量,并且确实可以增加它,即使CreateAction本身已经完成。