SomaMan
SomaMan

Reputation: 4164

Scope of class variables in c# lambdas

I have some code which waits for a server response & uses a lambda to do stuff when it gets it. It also checks a class ivar, _timedOut, in this lambda, to see what to do. What I'm not sure of is, if _timedOut is changed somewhere else in the class after the lambda was created but before it's invoked, what value of _timedOut will the lambda see?

I've trawled SO for answers to this, but none of the answers seem to address this specific query. Code -

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
}

Thanks for any help!

Upvotes: 0

Views: 618

Answers (3)

Jon Skeet
Jon Skeet

Reputation: 1500675

What I'm not sure of is, if _timedOut is changed somewhere else in the class after the lambda was created but before it's invoked, what value of _timedOut will the lambda see?

The lambda expression will be converted into an instance method, as you're effectively capturing the this reference by virtue of referring to an instance variable (and you're not capturing any of the local variables). The delegate created by the lambda expression will have a target of this, so when the delegate is executed, it will "see" any changes to _timedOut.

Of course this is still subject to normal thread safety issues - if one thread changes the value of a variable, without any extra synchronization or memory barriers it's possible for another thread to try to read that variable and see the old value.

Upvotes: 2

Peter Ritchie
Peter Ritchie

Reputation: 35870

Because no outer variables are deemed to need capturing, _timedOut not be "captured". What the compiler does is generate an instance method on the class in question and effectively "move" the code in the lambda into the instance method instead of creating a closure. For example, the compiler will generate an method on MyClass like this:

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

Ergo the code in the lambda will always directly access the _timedOut field (as well as the _databaseService field). If you accessed any local variable then the compiler would be forced to capture that and any other "outer variables" by generating a class to contain them, at which point this would be captured. For example, if we changed the code slightly: 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
                                            }
                                        }
                                        });
}
        }

The compiler would generate code to perform a capture in 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));
}

...clearly making a "copy" of this. Of course, even in this case because this can be nothing but a reference, when <>c__DisplayClass.<>4__this is referenced it's still referencing the original _timedOut directly.

Now, despite accessing the field directly, the compiler is still free to optimize its use of this variable because it is not accessed through a volatile read. This is independent of use of a lambda. If more that one thread came into play here, you may run into situations where the code may not see all the writes made to _timedOut on non x86/x64 architectures. You don't appear to be using multiple threads and you don't appear to be using _timedOut in a way that would cause the compiler to generate code that would not see updates to _timedOut on a different thread on x86/x64--but changing the code could introduce that.

Outer Variable

Any local variable, value parameter, or parameter array whose scope includes the lambda-expression or anonymous-method-expression is called an outer variable of the anonymous function. In an instance function member of a class, the this value is considered a value parameter and is an outer variable of any anonymous function contained within the function member.

Captured

When an outer variable is referenced by an anonymous function, the outer variable is said to have been captured by the anonymous function.

Upvotes: 0

Oded
Oded

Reputation: 499012

_timeout will be part of the closure over the lambda.

Meaning that the value in the lambda will be the value when it is invoked.

Upvotes: 3

Related Questions