闭包中的属性何时被评估?

8

我们代码库中的几种方法使用了'MaybeObject',当结果可能已知或依赖于尚未执行的外部webservice调用时,可以将其传递给函数。例如,下面的属性可以具有已知的指定值,或者如果在异步调用完成后调用且未指定,则返回异步调用的结果。

private string _internalString;
public string stringProp
{ 
    get
    {
        if (!string.IsNullOrEmpty(_internalString))
            return _internalString;
        return resultOfAsyncCallFromSomewhereElse;
    }

    set { _internalString = value; }
}

显然,在异步调用完成之前尝试引用该属性将导致空引用异常,因此我们还需要一个标志来检查该值是否可用。

问题是,在下面的代码中,lambda表达式的创建会尝试并评估stringProp(可能尚未填充),还是评估将被推迟,直到调用生成的Action(这将在检查异步操作完成后进行)?

public Action ExampleMethod(MaybeObject maybe)
{
    return () => doSomethingWithString(maybe.stringProp);
}

我的猜测是你不能确定,在规范中两种情况都是有效的。让我们看看完整的答案会说什么。 - CodingBarfield
6个回答

6
在C#中,所有内容都会在执行时被评估,因为C# lambda闭包不是真正的闭包,不能在创建时解决捕获环境的状态。
() => doSomethingWithString(maybe.stringProp);

这里甚至有引用 maybe 可以为空,直到执行委托,您不会遇到任何像NullReferenceException的问题。这个技巧有时用于延迟绑定值解析。

维基百科:闭包:

闭包保留了对创建时环境的引用(例如,对封闭作用域中局部变量的当前值),而通用匿名函数则不需要这样做。

C#闭包特定内容的好概述-C#中的匿名方法和闭包

根据闭包的定义可以推断出,在创建Closure时,它会记住变量的值。然而,在C#中,外部局部变量(在此情况下是i)与匿名方法共享,通过在堆上创建它来实现。这意味着对它在匿名方法中的任何更改都会更改原始值,并且当第二次调用该方法时,它会获得被修改后的i的值为1(请参见输出的第二行)。这使许多人认为匿名方法实际上不是一个闭包,因为根据其定义,闭包在创建时应该记住变量的值,并且不能修改。


2
+1 "不是真正的闭包" - 很有趣的观点,以前从未想过这样。 - Kirk Broadhurst

3
闭包中的属性在运行操作之前不被评估,因为属性是方法而不是值。访问MyObject.MyProperty就像调用代码MyObject.get_MyProperty()一样,因此当使用属性与使用基础变量时,可以获得不同的行为。

3

在调用操作(Action)之后才会执行评估。

委托引用的代码只有在显式调用委托本身时才会被执行,不管委托本身是如何创建的

例如,通过Action委托传递要执行的代码的这些方式都是等效的,在调用Action()之前,doSomethingWithString方法将不会被执行:

显式方法(.NET 1.1):

private MaybeObject maybe;

public Action ExampleMethod()
{
    return new Action(DoSomethingWithMaybeObject);
}

private void DoSomethingWithMaybeObject()
{
    doSomethingWithString(maybe.stringProp)
}

匿名方法(.NET 2.0):

public Action ExampleMethod(MaybeObject maybe)
{
    return delegate() { doSomethingWithString(maybe.stringProp) };
}

Lambda (.NET 3.5):

public Action ExampleMethod(MaybeObject maybe)
{
    return () => doSomethingWithString(maybe.stringProp);
}

另请参阅:


0
In .net closures are passed as reference so they are executed as they are evaluated, the same reason you can do something like this.


EventHandler handler = null;
handler = (sender, args) => { // event handler do something};
SomeEvent += handler;

0

它将被延迟执行。 ExampleMethod() 返回一个委托到匿名函数 () => doSomethingWithString(maybe.stringProp),因此只有在调用该委托后,doSomethingWithString() 才会被调用。


0

你应该自己尝试一下,看看会发生什么!

在这种情况下,闭包似乎真正围绕着MaybeObject,而不仅仅是字符串属性(因为对象被传递给了Action)。直到执行Action时才访问字符串属性。

但即使字符串本身是闭包的变量...

string s = "";
Func<bool> isGood = () => s == "Good"; 

s = "Good";
Console.WriteLine(isGood());

// prints 'True'

在这里,您可以看到闭包是围绕字符串创建的,但直到执行时才进行评估。

委托与任何其他函数一样工作-只有在调用它们时才会实际执行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接