Question

I am struggling to understand how to access local class variable in an action delegate when both the delegate is anonymous and the containing class is created just in time as an argument to a function.

The delegate seems to have access to calling functions variables, and it's containing class, but not to the class that actually contains the delegate. Please excuse me if I am not clear with my question, I am trying my best to articulate. Please refer to the code below:

public class QueryPropertyMessage
{
    public Action Callback { get; private set; }
    public string PropertyName { get; set; }
    public object PropertyValue { get; set; }

    public QueryPropertyMessage(Action callback)
    {
        this.Callback = callback;
    }

}

Class TestClass
{
    public void OuterTestFunction()
    {
        //the function will set PeropertyValue
        SomeClass.FunctionAcceptingQueryPropertyMessageObject (new QueryPropertyMessage (() =>
        {
            Helper.DebugHelper.TraceMessage ("Test Anonymous method");
            //Error The name 'PropertyValue' does not exist in the current context
            If(PropertyValue!=null)
                var val = Convert.ChangeType (PeropertyValue, typeof (bool));
        }) { PropertyName = "SomeBooleanProperty" });
    }
}

How do I access the QueryPropertyMessage object's properties inside the anonymous delegate? Reading Raymond Chen's article on anonymous methods, I seem to think that the delegate will be wrapped in another helper class, and hence what looks as containing class (QueryPropertyMessage) is not the local class from the delegates point of view. I would love to understand it little more clearly.

Was it helpful?

Solution

The action needs to be created before the QueryPropertyMessage has been created, or even defined, for you to be able to pass it into its constructor. There is therefore nothing to reference at that point in time. You need to create, or at least define a variable for, the object in question first.

QueryPropertyMessage message = new QueryPropertyMessage();
message.Callback = () =>
{
    var propertyName = message.PropertyName;
};

(This will of course require having a parameterless constructor and a setter for the callback.)

If you don't want to modify the class, then you can only define, and not initialize, the variable:

QueryPropertyMessage message = null;

message = new QueryPropertyMessage(() =>
    {
        var propertyName = message.PropertyName;
    });

That, or you need to pass the object itself as a parameter to the action:

public class QueryPropertyMessage
{
    public Action<QueryPropertyMessage> Callback { get; private set; }
    public string PropertyName { get; set; }
    public object PropertyValue { get; set; }

    public QueryPropertyMessage(Action<QueryPropertyMessage> callback)
    {
        this.Callback = callback;
    }
}

new QueryPropertyMessage(props =>
{
    var propertyName = props.PropertyName;
});

OTHER TIPS

It's somewhat painful to do so. You basically need to declare a local variable, give it a temporary value (so that it's definitely assigned) and then use that local variable:

QueryPropertyMessage message = null;
message = new QueryPropertyMessage(() =>
{
    // Use message in here
});
SomeClass.FunctionAcceptingQueryPropertyMessageObject(message);

This is relying on the callback not being called in the constructor - otherwise it will see the original null value of message.

(Alternatively, you could change the callback to receive the message, of course.)

You can't do that. The delegate has no idea that it's being passed to a constructor. That's like an int being aware of the type that contains it.

One way to solve this is to make your setter public, create the instance of QueryPropertyMessage, and then set the delegate.

var query = new QueryPropertyMessage();

query.Callback = () =>
        {
            Helper.DebugHelper.TraceMessage ("Test Anonymous method");
            If(query.PropertyValue!=null)
                var val = Convert.ChangeType (PeropertyValue, typeof (bool));
        };

This way, the lambda will close over your instance of QueryPropertyMessage and have access to it.

I seem to think that the delegate will be wrapped in another helper class

Only if it needs to close over (look up closures) one or more variables. In the code above, that will happen.

The compiler will create a class that has your instance of QueryPropertyMessage as a field, and a method that implements the lambda, something like this:

public class <Lambda>b__0
{
    private readonly QueryPropertyMessage _query = ...

    public void Execute()
    {
        Helper.DebugHelper.TraceMessage ("Test Anonymous method");
        If(_query.PropertyValue!=null)
            var val = Convert.ChangeType (PeropertyValue, typeof (bool));
    }
}

Solution for your problem will be you need to change the Action<> type to Action<QueryPropertyMessage> Callback

class QueryPropertyMessage
{
    public Action<QueryPropertyMessage> Callback { get; private set; }
    public string PropertyName { get; set; }
    public object PropertyValue { get; set; }

    public QueryPropertyMessage(Action<QueryPropertyMessage> callback)
    {
        this.Callback = callback;
    }

}

public class TestClass
{
    public void OuterTestFunction()
    {

        //the function will set PeropertyValue
        SomeClass.FunctionAcceptingQueryPropertyMessageObject (new QueryPropertyMessage ((item) =>
        {
            Helper.DebugHelper.TraceMessage ("Test Anonymous method");
            //Error The name 'PropertyValue' does not exist in the current context
            If(item.PropertyValue!=null)
                var val = Convert.ChangeType (PeropertyValue, typeof (bool));
        }) { PropertyName = "SomeBooleanProperty" });
    }
}
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top