c#lambdas中类变量的范围

时间:2012-09-24 14:59:52

标签: c# lambda scope

我有一些等待服务器响应的代码&当它得到它时,使用lambda来做东西。它还会检查此lambda中的类ivar,_ timedOut,以查看要执行的操作。我不确定的是,如果在创建lambda之后在类中的其他地方更改了_timedOut但是在调用它之前,lambda会看到_timedOut的值是什么?

我已经搜索了SO的答案,但没有一个答案似乎解决了这个特定的查询。代码 -

public class MyClass
{
    public MyClass()
    {
        _databaseService = //...database stuff
        _uploadService = //...uploads info
        _serverService = //...gets stuff from the server

        _uploadService.UploadingStatusChanged += UploadStatusChanged; 
    }

    private bool _timedOut = false;


    private void GetFinalInfo()
    {
        FinalInfo finalInfo = _databaseService.GetFinalInfo();

        if (finalInfo == null) // still have no finalInfo
        {

            _serverService.GetLatestFinalInfo((response, theFinalInfo) =>
            {
                if (!_timedOut) // this could be changed elsewhere in the class while we're waiting for the server response
                {
                    if (response == ServerResponse.Successful)
                    {
                        _databaseService.AddFinalInfo(theFinalInfo);
                        // navigate to next screen
                    }
                    else
                    {
                        // do something else
                    }
                }
            });
        }
        else
        {
            // navigate to next screen
        }
    }

}


private void UploadStatusChanged(object s, MyEventArgs e)
{
    // do stuff & call GetFinalInfo if good
}

感谢您的帮助!

3 个答案:

答案 0 :(得分:3)

_timeout将成为关于lambda的闭包的一部分。

这意味着当调用时,lambda中的值将是的值。

答案 1 :(得分:2)

  

我不确定的是,如果在创建lambda之后在类中的其他地方更改了_timedOut但是在调用它之前,lambda会看到_timedOut的值是什么?

lambda表达式将转换为实例方法,因为您通过引用实例变量有效捕获this引用(并且您没有捕获任何局部变量)。由lambda表达式创建的委托将具有this的目标,因此当委托执行时,它将“看到”对_timedOut的任何更改。

当然,这仍然存在正常的线程安全问题 - 如果一个线程更改了变量的值,没有任何额外的同步或内存障碍,则另一个线程可能会尝试读取该变量并查看旧值。 / p>

答案 2 :(得分:0)

因为没有外部变量被认为需要捕获,_timedOut不会被“捕获”。编译器所做的是在有问题的类上生成实例方法,并有效地将lambda中的代码“移动”到实例方法中,而不是创建闭包。例如,编译器将在MyClass上生成一个方法,如下所示:

[CompilerGenerated]
private void <GetFinalInfo>b__0(ServerResponse response, object theFinalInfo)
{
    if (!this._timedOut)
    {
        if (response == ServerResponse.Successful)
        {
            this._databaseService.AddFinalInfo(theFinalInfo);
        }
    }
}

因为lambda中的代码将始终直接访问_timedOut字段(以及_databaseService字段)。如果您访问了任何局部变量,那么编译器将被强制通过生成包含它们的类来捕获该变量以及任何其他“外部变量”,此时将捕获this。例如,如果我们稍微更改了代码:     FinalInfo finalInfo = _databaseService.GetFinalInfo();     MyStruct myStruct = new MyStruct();     myStruct.i = 1;

if (finalInfo == null) // still have no finalInfo
{

    _serverService.GetLatestFinalInfo((response, theFinalInfo) =>
                                        {
                                            Trace.WriteLine(myStruct.i);
                                        if (!_timedOut) // this could be changed elsewhere in the class while we're waiting for the server response
                                        {
                                            if (response == ServerResponse.Successful)
                                            {
                                                _databaseService.AddFinalInfo(theFinalInfo);
                                                // navigate to next screen
                                            }
                                            else
                                            {
                                                // do something else
                                            }
                                        }
                                        });
}
        }

编译器将生成代码以在GetFinalInfo中执行捕获:

MyClass.<>c__DisplayClass2 <>c__DisplayClass = new MyClass.<>c__DisplayClass2();
<>c__DisplayClass.<>4__this = this;
FinalInfo finalInfo = this._databaseService.GetFinalInfo();
<>c__DisplayClass.bleah = default(MyStruct);
<>c__DisplayClass.bleah.i = 1;
if (finalInfo == null)
{
    this._serverService.GetLatestFinalInfo(new Action<ServerResponse, object>(<>c__DisplayClass.<GetFinalInfo>b__0));
}

...明确地制作this的“副本”。当然,即使在这种情况下,因为this只能作为引用,但在引用<>c__DisplayClass.<>4__this时,它仍然直接引用原始_timedOut

现在,尽管直接访问该字段,编译器仍然可以自由地优化其对此变量的使用,因为它不是通过易失性读取来访问的。这与使用lambda无关。如果在这里发挥了更多的一个线程,您可能会遇到代码可能看不到在非x86 / x64体系结构上对_timedOut进行的所有写入的情况。您似乎没有使用多个线程,并且您似乎没有使用_timedOut的方式导致编译器生成的代码在另一个线程上看不到_timedOut的更新在x86 / x64上 - 但更改代码可能会引入它。

外部变量

  

其范围包括lambda-expression或anonymous-method-expression的任何局部变量,值参数或参数数组称为匿名函数的外部变量。在类的实例函数成员中,此值被视为值参数,并且是函数成员中包含的任何匿名函数的外部变量。

捕获

  

当外部变量被匿名函数引用时,外部变量据说已被匿名函数捕获。