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

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


当前回答

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

希望这对大家有所帮助。

其他回答

闭包是在函数中定义的函数,可以访问它的局部变量以及它的父变量。

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方法作为委托从另一个作用域执行的。

这是c# 7.0概要书中一个简单易懂的答案。

您应该知道的先决条件: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
}

实部分:lambda表达式引用的外部变量称为捕获变量。捕获变量的lambda表达式称为闭包。

最后需要注意的一点:被捕获的变量是在委托实际调用时计算的,而不是在变量被捕获时计算的:

int factor = 2;
Func<int, int> multiplier = n => n * factor;
factor = 10;
Console.WriteLine (multiplier (3)); // 30

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

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

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

闭包的一般特性是通过匿名方法和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本身已经完成。

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.