【问题标题】:What is so special about closures?闭包有什么特别之处?
【发布时间】:2009-04-17 10:07:03
【问题描述】:

我去过reading this article about closures,他们说:

  • “所有管道都是自动的”
  • 编译器“创建一个包装类”并“延长变量的寿命”
  • “您可以放心使用局部变量”
  • .NET 编译器会为您处理管道等问题。

所以我根据他们的代码做了一个例子,对我来说,似乎闭包的行为类似于常规命名方法,它们也“无忧无虑地处理局部变量”并且“所有管道都是自动的” .

或者说这个“局部变量的包装”解决了什么问题让闭包如此特别/有趣/有用?

using System;
namespace TestingLambda2872
{
    class Program
    {
        static void Main(string[] args)
        {
            Func<int, int> AddToIt = AddToItClosure();

            Console.WriteLine("the result is {0}", AddToIt(3)); //returns 30
            Console.ReadLine();
        }

        public static Func<int, int> AddToItClosure()
        {
            int a = 27;
            Func<int, int> func = s => s + a;
            return func;
        }
    }
}

回答

所以这个问题的答案是阅读 Marc 指出的Jon Skeet's article on closures。本文不仅展示了 C# 中 lambda 表达式的演变过程,还展示了 Java 中如何处理闭包,这是本主题的绝佳读物。

【问题讨论】:

  • 其他语言(如 Javascript)支持闭包。你问的是概念还是C#的具体实现?
  • 我认为这个例子太简单了,无法理解闭包的威力。
  • @strager:我的意思是一般的概念,我试图让我的头脑围绕委托、闭包、映射、lambda、“lambda 表达式”、“lambda 树”、匿名函数、currying、等等,一般来说,似乎超越任何一种语言的函数式编程的新范式,都源于数学等。(我把 C# 放在标题中,因为 Stackoverflow 现在告诉你你的标题“不够好,添加独特的词"...)
  • 天啊,C# 4.0 我不知道!谢谢你。

标签: c# closures


【解决方案1】:

您的示例不清楚,并且 (IMO) 没有显示典型的捕获用法(捕获的唯一内容是 a,它始终为 3,所以不是很有趣)。

考虑这个教科书示例(谓词):

List<Person> people = ...
string nameToFind = ...
Person found = people.Find(person => person.Name == nameToFind);

现在在没有闭包的情况下试一试;即使我们很懒,你也需要做更多的工作:

PersonFinder finder = new PersonFinder();
finder.nameToFind = ...
Person found = people.Find(finder.IsMatch);
...
class PersonFinder {
    public string nameToFind; // a public field to mirror the C# capture
    public bool IsMatch(Person person) {
        return person.Name == nameToFind;
    }
}

捕获方法进一步扩展到不同范围内的大量变量 - 很多隐藏的复杂性。

除了名称之外,以上是 C# 编译器在幕后所做的近似。请注意,当涉及其他范围时,我们开始链接不同的捕获类(即内部范围具有对外部范围的捕获类的引用)。相当复杂。

Jon Skeet 有一个很好的article on this here,还有更多in his book

【讨论】:

  • (对于已删除的关于使其成为 static 的问题):否 - 因为不同的线程可能希望同时搜索不同的名称,或者推迟执行委托。
  • +1 在 C# in Depth 闭包文章中的这句话:“闭包允许您封装一些行为,像任何其他对象一样传递它,并且仍然可以访问它们最初所在的上下文声明”
  • 好吧,我不能把文章中的内容归功于我 - 你可能想在某处给 Jon 一个 +1 ;-p
  • 这是一篇很棒的文章,在代码中展示了 C# 从谓词 (1) 到匿名方法 (2) 到 lambda 表达式 (3) 的演变,同时还展示了等效的 java 代码,我将通过购买他的 C# in Depth 书给 Jon 加分;-p
  • 另一种选择,如果编写了一些通用的样板函数,那就是Person found = People.Find(BindFunc&lt;Bool&gt;.Bind((Person p, string st) =&gt; (p.Name == st), nametoFind));。语法有点笨拙,但恕我直言语义得到了改进;我猜想,在按值捕获与按引用捕获不同的 90% 情况下,按值捕获将是正确的做法)。
【解决方案2】:

闭包是编译器的一个功能。你看不到它,它只是让你编写的代码工作。

没有它,对 AddToIt(3) 的调用将失败,因为底层 lamda 在 AddToItClusure() 的范围内使用局部变量 a = 27。调用 AddToIt 时该变量不存在。

但是由于闭包,编译器使用的一种机制,代码可以工作,你不必关心它。

【讨论】:

    猜你喜欢
    • 2011-05-19
    • 2011-08-15
    • 1970-01-01
    • 1970-01-01
    • 2014-08-19
    • 2011-04-18
    • 2017-02-19
    • 2016-09-30
    相关资源
    最近更新 更多