由C#中的lambda创建的委托的生存期是多少

本文关键字:生存期 多少 中的 lambda 创建 | 更新日期: 2023-09-27 17:58:52

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

当我想知道它们是如何工作的时,我直观地认为它们可能只创建过一次。这激发了我创建一个解决方案的灵感,该解决方案允许通过使用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#中的lambda创建的委托的生存期是多少

这两种方式都不能保证。

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

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

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

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

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

  • 支持给定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进行反向工程。它将为你提供大量关于代码如何工作的信息。

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

我至少给你投了赞成票,所以希望有人能给你你想要的学术答案。