0

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!

0

3 Answers 3

3

_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.

Sign up to request clarification or add additional context in comments.

Comments

2

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.

11 Comments

I view "capturing" as something that occurs in a closure. "capturing" captures the context at the time of closure. This lambda, as you say, generates an instance method on the class--which really doesn't "capture" anything. As you allude to, the reference to _timedOut will be directly off this and does not "capture" this.
@PeterRitchie: I think the language specification disagrees. Section 7.15.5 of the C# 5 spec states: "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." - and the following section then talks about when outer variables are captured. To put it another way, it's only an implementation detail that the lambda expression is converted into an instance method. The compiler could create a nested type and capture the this value like any other parameter.
@PeterRitchie At the time of closure this was in the context and referenced in the lambda, the mechanism used to capture it in the closure was by tacking the lambda onto the class as an instance method giving it privileges to this. I realize this is a bit different than how an actual closure acts, but an actual closure is based on value semantics which are lost in the reference semantics of C#. How would one capture the value of a complex type without a full deep copy for instance? Which is not something inherently available to C#.
@Jon Yes, it does consider this to be an outer variable and illegible for capturing--but, in this case it doesn't actually capture it. Yes, the compiler could generate another type (nested or otherwise) and capture this but it doesn't, as you point out. In the semantics of a class it's fairly meaningless, if it did capture this it would still reference the original.
@JimmyHoffa In the case of capturing a struct, the compiler knows the fields so it will generate code to perform field-by-field copy when it captures. I presume it might optimize that to only include the struct's fields that are in use in the lambda...
|
0

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.

7 Comments

Your post contradicts the sections of the spec you've quoted: this is an outer variable, and it's implicitly referenced in the code, therefore it's a captured variable. The fact that the compiler works out that when the only captured variable is this it can just create an instance method is an implementation detail and doesn't affect the use of terminology, IMO. Can you reason that this isn't captured using the specification? A compliant implementation could just as easily create a new nested class even when the current one generates an instance method.
@Jon my only real point is that the compiler doesn't generate any code where this is ever assigned to a generated field thus not really "captured".
(Copied from comment previously on my answer.) You're now defining "captured" in terms of an implementation detail though. Where does that definition come from? There's a very clear definition (the spec, which you quoted) whereby it does count as a captured variable. Why redefine a term?
I think you're getting caught up in where a lambda isn't quite exactly a closure due to the reference semantics of C#. Yes when dealing with value types lambdas may become closures. But knowing C# has neither deep copy nor maintains mutation deltas, you can conclude there is no implementation technique capable of creating a true closure over an outer variable which is a reference type.
@JonSkeet It's not a C# term. I'm not saying this isn't a potential outer variable, and I won't dispute the spec details outer variables referenced by an anonymous method are "captured". But the compiler does not generate a closure and thus doesn't "capture" in most other other definitions of "closure".
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.