什么是闭包?我们在。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

希望这对大家有所帮助。

其他回答

下面是我从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#如何实现闭包,请阅读“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);
    }
}

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

闭包是在另一个函数(或方法)中定义的函数,它使用父方法中的变量。这种位于方法中并包装在其中定义的函数中的变量的使用称为闭包。

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#可以以匿名委托的形式使用它们。

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

delegate int testDel();

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

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

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