2010-11-09 123 views
5

考虑这个代码片段,并试着猜测什么y1y2评估为为什么这两个函数不会返回相同的值?

static class Extensions 
{ 
    public static Func<T> AsDelegate<T>(this T value) 
    { 
     return() => value; 
    } 
} 
class Program 
{ 
    static void Main(string[] args) 
    { 
     new Program(); 
    } 

    Program() 
    { 
     double x = Math.PI; 

     Func<double> ff = x.AsDelegate(); 
     Func<double> fg =() => x; 

     x = -Math.PI; 

     double y1 = ff(); // y1 = 3.141.. 
     double y2 = fg(); // y2 = -3.141.. 

    } 
} 

你可能会说-Aha-双是一个值类型,因此通过扩展方法的返回值是的一个副本主要是x。但是当你将上面的代码改为类的代表时,结果仍然不同。例如:

class Foo 
{ 
    public double x; 
} 
    Program() 
    { 
     Foo foo = new Foo() { x=1.0 }; 

     Func<Foo> ff = foo.AsDelegate(); 
     Func<Foo> fg =() => foo; 

     foo = new Foo() { x = -1.0 }; 

     double y1 = ff().x; // y1 = 1.0 
     double y2 = fg().x; // y2 = -1.0 
    } 

所以这两个函数必须返回同一个类的两个不同实例。有趣的是,考虑到ff()带有对局部变量foo的引用,但fg()没有,并且它依赖于当前范围内的内容。

那么当这两个代表被传递到代码的其他部分时,如果foo实例不具有可见性,会发生什么情况?不管怎样,当扩展方法与代表组合时,谁拥有什么信息(数据)变得越来越不明确。

回答

3

ff捕获(结合)至x在这一行:

Func<double> ff = x.AsDelegate(); 

相比之下,fg结合到可变x在这一行:

Func<double> fg =() => x; 

所以,当x的值改变时,ff不受影响,但fg更改。

+0

点击此答案,因为简洁+清晰度+格式。 – ja72 2010-11-09 18:21:04

+0

咩...这一个:不绑定到一个*值*的东西(除了编译器生成捕获类的实例),和b:不严格绑定到“X” *都* - 更清晰的是,但由于这种简单性,我认为可能有点误导。 (虽然简单很好) – 2010-11-09 18:39:56

+0

@Marc - 这是一个折腾。我想着重于用户看起来的方式,而不是深入实施。授予 – Bevan 2010-11-09 20:28:02

2

() => x捕获x值。编译器创建特殊类以处理lambda或匿名委托,捕获lambda中使用的任何变量。

例如如果您运行下面的代码:

List<Func<int>> list = new List<Func<int>>(); 

    for (int i = 0; i < 5; i++) 
    { 
     list.Add(() => i); 
    } 

    list.ForEach(function => Console.WriteLine(function())); 

你会看到,印刷数字是相同的。

+0

所以,当函数()被调用,它使用我有那个时间(外循环)的任何值?我认为'i'的范围在循环结束时结束...... – ja72 2010-11-09 18:14:59

7

AsDelegate捕获可变value(的AsDelegate参数),而() => x捕获可变x。所以如果你改变x的值,lambda表达式将返回一个不同的值。虽然更改x不会更改value

请参见:Outer Variable Trap

+0

严格地说,它捕获*参数*值;其价值最初是x的价值,永远不会改变。细微的区别... – 2010-11-09 18:07:35

3

AsDelegate扩展方法使用的x值在时间AsDelegate叫做而lambda表达式() => x捕获可变x并且因此x的值在表达式被调用的时间(而不是值时,它取被定义)。

4

在AsDelegate中,我们正在捕获参数“value”。此方法的值被视为调用方法时变量值的副本,并且永远不会更改 - 因此我们可以看到原始对象。

直接拉姆达捕捉变量 FOO(不是值的变量的 - 变量本身) - 因此,我们看到改变了这一点。

基本上添加一个方法调用改变了被捕获的大腿。

0

第一方法创建一个新的委托于给定功能,并且存储它。它后来覆盖foo,你新创建的委托没有被触及。

第二个是一个拉姆达表达其升降机/* *捕获它的上下文,这意味着foo的变量。该lambda表达式可以看到所有由lambda表达式提升的变量。

相关问题