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

在C#中由lambda创建的委托的生存期是多少?,c#,delegates,lambda,C#,Delegates,Lambda,兰姆达斯很不错,因为他们提供和。不必编写只使用一次的函数,您可以使用lambda 在想它们是如何工作的时候,我直觉地认为它们可能只创建了一次。这启发我创建了一个解决方案,通过使用lambda作为创建它的范围的标识符,它允许创建一个特定的范围 这个实现是有效的,尽管可能有些过分(仍在研究),证明了我的假设是正确的 一个较小的例子: class SomeClass { public void Bleh() { Action action = () => {};

兰姆达斯很不错,因为他们提供和。不必编写只使用一次的函数,您可以使用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()}}
    }
}
class-SomeClass
{
公共空间Bleh()
{
动作动作=()=>{};
}
公共无效CallBleh()
{
Bleh();//`action`={Method={Void b__0()}
Bleh();//`action`still=={Method={Void b__0()}}
}
}
lambda是否会返回一个新实例,或者它是否保证总是相同的?

这两种方式都不能保证

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

  • 不捕获任何变量的lambda表达式是静态缓存的
  • 仅捕获“this”的lambda表达式可以在每个实例的基础上捕获,但不是
  • 无法缓存捕获局部变量的lambda表达式
  • 具有完全相同程序文本的两个lambda表达式没有别名;在某些情况下,它们可能是,但确定它们可能处于的情况将非常复杂
  • 编辑:正如埃里克在评论中指出的,您还需要考虑为泛型方法捕获的类型参数。
编辑:C#4规范的相关文本见第6.5.1节:

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


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


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

我在回答的时候看到Skeet插嘴了,所以我不会再重复这一点。为了更好地理解您是如何使用这些东西,我建议您熟悉逆向工程工具和IL。将有问题的代码样本和逆向工程提交给IL。它将为您提供大量有关代码如何工作的信息。

不保证

快速演示:

Action GetAction()
{
    return () => Console.WriteLine("foo");
}
调用两次,执行a
ReferenceEquals(a,b)
,您将得到
true

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

打两次电话,做a
ReferenceEquals(a,b)
,你会得到
false

根据你的问题和你对Jon答案的评论,我认为你混淆了很多事情。为了确保清楚,请执行以下操作:

  • 为给定lambda支持委托的方法始终相同
  • 为词汇上出现两次的“相同”lambda支持委托的方法是允许相同的,但实际上在我们的实现中是不同的
  • 为给定lambda创建的委托实例可能总是相同的,也可能不总是相同的,这取决于编译器缓存它的智能程度
因此,如果您有类似以下内容:

for(i = 0; i < 10; ++i)
    M( ()=>{} )
然后,在实践中,编译器会将其生成为

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;

现在明白了吗?

他们所说的“相同效果”到底是什么意思?调用
GetCurrentMethod
显然没有相同的效果……这听起来很对,不过如果lambda“捕获”了泛型方法的类型参数,事情也会变得有点棘手。即使它不使用局部变量或参数,也可能无法在静态字段中缓存。@Eric:使用
公共静态本地实例(Func scope)
,其中
()=>此
传递给scope。在这种情况下,它仍然返回同一个委托。我现在非常不喜欢“(但不是必需的)”,但是哦,我得到了我的答案@史蒂文:我向你保证,如果你创建两次“()=>this”,它不会返回同一个委托。如果这是你得到的结果,那你就做错了。您如何尝试比较代理的相等性?你知道委托有值,而不是引用相等,对吧?这证实了Jon的回答:“捕获局部变量的lambda表达式不能被缓存”。@Steven还有,“不捕获任何变量的lambda表达式是静态缓存的。”你的第一句话很好。我将使用这个技巧:)非常有用!但我对“允许”有点困惑。你是说现在不是,但将来可能会不同?我运行了代码
actiona1=()=>{};动作a2=()=>{};Console.WriteLine(object.ReferenceEquals(a1,a2))返回false。@Jenix:我说的“允许”是指允许C#编译器的作者编译您的程序,使其返回
true
false
,由编译器作者自行决定。您不能依赖编译器有一个或另一个行为,因为这被记录为允许随时更改。
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;
static void MyAction1() {}
static Action ActionCache1 = null;
...
if (ActionCache1 == null) ActionCache1 = new Action(MyAction1);
Action a1 = ActionCache1;
Action a2 = ActionCache1;