繁体   English   中英

由 C# 中的 lambda 创建的委托的生命周期是多少?

[英]What is the lifetime of a delegate created by a lambda in C#?

Lambda 很好,因为它们提供简洁性和局部性以及额外的封装形式 不必编写只使用一次的函数,您可以使用 lambda。

虽然想知道它们是如何工作的,但我直觉地认为它们可能只创建一次 This inspired me to create a solution which allows to restrict the scope of a class member beyond private to one particular scope by using the lambda as an identifier of the scope it was created in.

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

一个较小的例子:

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 会返回一个新实例,还是保证始终相同?

无论哪种方式都不能保证

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

  • 不捕获任何变量的 lambda 表达式被静态缓存
  • 仅捕获“this”的 lambda 表达式可以在每个实例的基础上捕获,但不是
  • 无法缓存捕获局部变量的 lambda 表达式
  • 两个具有完全相同程序文本的 lambda 表达式没有别名; 在某些情况下,它们可能是,但找出它们可能的情况将非常复杂
  • 编辑:正如 Eric 在评论中指出的那样,您还需要考虑为通用方法捕获类型arguments。

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

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

根据您在这里的问题和您对乔恩回答的评论,我认为您混淆了很多事情。 为确保清楚:

  • 支持给定 lambda 的委托的方法始终相同。
  • 支持在词法上出现两次的“相同”lambda 的委托的方法允许相同,但实际上在我们的实现中并不相同。
  • 为给定的 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 GetAction()
{
    return () => Console.WriteLine("foo");
}

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

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

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM