在C#中使用lambda创build的委托的生命周期是什么?
Lambdas很好,因为它们提供了简洁和本地化以及一个额外的封装forms 。 而不必编写只能用于lambda的函数。
在想知道他们是如何工作的时候,我直觉地认为他们可能只创build一次 。 这启发了我创build了一个解决scheme,它允许通过使用lambda作为其创build范围的标识符,将类成员的范围限制在一个特定范围之外 。
这个实现虽然可能是矫枉过正的(仍在研究它),但certificate了我的假设是正确的。
一个较小的例子:
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是否会返回一个新的实例,还是总是保持不变?
根据你在这里的问题和你对Jon的回答的评论,我认为你混淆了多个东西。 为了确保清楚:
- 支持给定lambda的委托的方法总是相同的。
- 将代表“相同”的lambdaexpression式按照词法顺序排列两次的方法是允许的,但实际上在我们的实现中是不一样的。
- 为给定的lambda创build的委托实例可能总是相同,也可能不总是相同的,这取决于编译器是如何巧妙地将其caching的。
所以如果你有这样的东西:
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;
现在清楚了吗?
这不是保证任何方式。
从我记得目前的MS实施:
- 不捕获任何variables的lambdaexpression式被静态caching
- 只捕获“this”的lambdaexpression式可以基于每个实例捕获,但不是
- 捕获局部variables的lambdaexpression式不能被caching
- 具有完全相同程序文本的两个lambdaexpression式不是别名; 在某些情况下,他们可能是,但是解决他们可能会变得非常复杂的情况
- 编辑:正如埃里克在评论中指出的,你还需要考虑为generics方法捕获的types参数。
编辑:C#4规范的相关文本是在6.5.1节:
语义上相同的匿名函数与相同的(可能是空的)一组捕获的外部variables实例到相同的委托types的转换被允许(但不是必需的)返回相同的委托实例。 术语语义相同在这里用来表示匿名函数的执行在任何情况下都会在给定相同的参数的情况下产生相同的效果。
没有保证。
快速演示:
Action GetAction() { return () => Console.WriteLine("foo"); }
调用这个两次,做一个ReferenceEquals(a,b)
,你会ReferenceEquals(a,b)
true
Action GetAction() { var foo = "foo"; return () => Console.WriteLine(foo); }
调用这个两次,做一个ReferenceEquals(a,b)
,你会得到false
当我回答的时候,我看到Skeet跳了进去,所以我不会这么说。 为了更好地理解你如何使用事物,我build议的一件事就是熟悉逆向工程工具和IL。 取出有问题的代码示例并将其反向工程为IL。 它会给你提供大量有关代码如何工作的信息。
好问题。 我没有一个“学术答案”,更多的是一个实际的答案:我可以看到一个编译器优化二进制使用相同的实例,但我永远不会写代码,假设它“保证”是相同的实例。
我至less鼓励你,所以希望有人能给你你想要的学术答案。