【问题标题】:What is the lifetime of a delegate created by a lambda in C#?由 C# 中的 lambda 创建的委托的生命周期是多少?
【发布时间】:2011-06-08 14:44:51
【问题描述】:

Lambda 很不错,因为它们提供 brevity and localityan extra form of encapsulation。不必编写仅在一次可以使用 lambda 时使用的函数。

虽然想知道它们是如何工作的,但我直觉地认为它们可能只创建一次。这启发了我创建一个解决方案,它允许 to restrict the scope of a class member beyond private 通过使用 lambda 作为创建它的范围的标识符来进入一个特定范围。

这个实现可行,虽然可能有点矫枉过正(仍在研究它),证明我的假设是正确的。

一个更小的例子:

class SomeClass
{
    public void Bleh()
    {
        Action action = () => {};
    }

    public void CallBleh()
    {
        Bleh();  // `action` == {Method = {Void <SomeClass>b__0()}}
        Bleh();  // `action` still == {Method = {Void <SomeClass>b__0()}}
    }
}

lambda 会返回一个新实例,还是保证始终相同?

【问题讨论】:

    标签: c# delegates lambda


    【解决方案1】:

    这不是保证无论哪种方式。

    根据我对当前 MS 实现的记忆:

    • 不捕获任何变量的 lambda 表达式被静态缓存
    • 仅捕获“this”的 lambda 表达式可以按实例捕获,但不能
    • 无法缓存捕获局部变量的 lambda 表达式
    • 具有完全相同的程序文本的两个 lambda 表达式没有别名;在某些情况下,他们可能,但找出他们可能的情况会非常复杂
    • 编辑:正如 Eric 在 cmets 中指出的那样,您还需要考虑为泛型方法捕获的 type 参数。

    编辑:C# 4 规范的相关文本在第 6.5.1 节中:

    允许(但不要求)将具有相同(可能为空)捕获的外部变量实例集的语义相同的匿名函数转换为相同的委托类型,以返回相同的委托实例。这里使用的术语语义相同是指匿名函数的执行在所有情况下都会在给定相同参数的情况下产生相同的效果。

    【讨论】:

    • “相同的效果”究竟是什么意思?调用GetCurrentMethod 显然没有同样的效果...
    • 这听起来对我来说是正确的,但如果 lambda “捕获”泛型方法的类型参数,事情也会变得有点棘手。即使它不使用本地或参数,它仍然可能无法缓存在静态字段中。
    • @Eric: One of my test cases 使用public static Local&lt;TValue&gt; Instance&lt;TScope&gt;( Func&lt;TScope&gt; scope ),其中() =&gt; this 被传递到作用域。在这种情况下,它仍然返回相同的委托。
    • 我现在非常不喜欢 "(但不是必需的)",但是哦,好吧,我得到了答案。 :)
    • @Steven:我向您保证,如果您创建两次“()=>this”,它不会返回同一个委托。如果这是你得到的结果,那么你做错了什么。您如何尝试比较代表的平等性? 您知道委托具有价值,而不是引用平等,对吧?
    【解决方案2】:

    根据您在此处提出的问题以及您对 Jon 的回答的评论,我认为您混淆了很多事情。为了确保清楚:

    • 为给定 lambda 支持委托的方法始终相同。
    • 支持在词法上出现两次的“相同”lambda 的委托的方法允许相同,但实际上 em> 在我们的实现中也是如此。
    • 为给定 lambda 创建的 委托实例 可能始终相同,也可能不同,具体取决于编译器缓存它的智能程度。

    所以如果你有类似的东西:

    for(i = 0; i < 10; ++i)
        M( ()=>{} )
    

    那么每次调用 M 时,你都会得到委托的相同的实例,因为编译器很聪明并且会生成

    static void MyAction() {}
    static Action DelegateCache = null;
    
    ...
    for(i = 0; i < 10; ++i)
    {
        if (C.DelegateCache == null) C.DelegateCache = new Action ( C.MyAction )
        M(C.DelegateCache);
    }
    

    如果你有

    for(i = 0; i < 10; ++i)
        M( ()=>{this.Bar();} )
    

    然后编译器生成

    void MyAction() { this.Bar(); }
    ...
    for(i = 0; i < 10; ++i)
    {
        M(new Action(this.MyAction));
    }
    

    您每次都会使用相同的方法获得一个新的委托。

    编译器被允许生成(但实际上此时并没有)

    void MyAction() { this.Bar(); }
    Action DelegateCache = null;
    ...
    for(i = 0; i < 10; ++i)
    {
        if (this.DelegateCache == null) this.DelegateCache = new Action ( this.MyAction )
        M(this.DelegateCache);
    }
    

    在这种情况下,如果可能,您将始终获得相同的委托实例,并且每个委托都将由相同的方法支持。

    如果你有

    Action a1 = ()=>{};
    Action a2 = ()=>{};
    

    然后在实践中编译器将其生成为

    static void MyAction1() {}
    static void MyAction2() {}
    static Action ActionCache1 = null;
    static Action ActionCache2 = null;
    ...
    if (ActionCache1 == null) ActionCache1 = new Action(MyAction1);
    Action a1 = ActionCache1;
    if (ActionCache2 == null) ActionCache2 = new Action(MyAction2);
    Action a2 = ActionCache2;
    

    但是编译器被允许检测到两个 lambda 是相同的并生成

    static void MyAction1() {}
    static Action ActionCache1 = null;
    ...
    if (ActionCache1 == null) ActionCache1 = new Action(MyAction1);
    Action a1 = ActionCache1;
    Action a2 = ActionCache1;
    

    现在清楚了吗?

    【讨论】:

    • 内容丰富!但我对“允许”有点困惑。你的意思是不是现在,但将来可能会有所不同?我运行了代码Action a1 = () =&gt; {}; Action a2 = () =&gt; {}; Console.WriteLine(object.ReferenceEquals(a1, a2));,结果返回false。
    • @Jenix:“允许”是指允许 C# 编译器的作者编译您的程序,以便编译器作者自行决定返回 truefalse。您不能依赖编译器具有一种或另一种行为,因为这被记录为允许随时更改。
    【解决方案3】:

    没有保证。

    快速演示:

    Action GetAction()
    {
        return () => Console.WriteLine("foo");
    }
    

    调用这个两次,做一个ReferenceEquals(a,b),你会得到true

    Action GetAction()
    {
        var foo = "foo";
        return () => Console.WriteLine(foo);
    }
    

    调用两次,执行ReferenceEquals(a,b),您将收到false

    【讨论】:

    • 这证实了 Jon 的回复:“无法缓存捕获局部变量的 lambda 表达式”。
    • @Steven 还有,“一个不捕获任何变量的 lambda 表达式被静态缓存。”
    【解决方案4】:

    我看到 Skeet 在我回答的时候跳了进来,所以我不会详细说明这一点。我建议的一件事是,为了更好地理解你是如何使用事物的,那就是熟悉逆向工程工具和 IL。获取有问题的代码示例并逆向工程到 IL。它将为您提供有关代码如何工作的大量信息。

    【讨论】:

      【解决方案5】:

      好问题。我没有“学术答案”,更多的是实际答案:我可以看到编译器优化二进制文件以使用相同的实例,但我永远不会编写假设它“保证”为相同实例的代码.

      至少我支持你,所以希望有人能给你你正在寻找的学术答案。

      【讨论】:

        猜你喜欢
        • 2011-12-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-09-25
        相关资源
        最近更新 更多