Refer to the properties of ancestors when instantiating and extending a class "in one shot"

60 views Asked by At

Thanks to the answers to this question I am able to override a virtual method in C# when I extend a class into a kind of anonymous type.

But is it possible to refer to a class variable or property of the ancestor class?

Here is a simplified version of my C# code:

public class MyAsyncBackgroundJob<TExecuteArgs> {

    public Func<TExecuteArgs, Task> ExecutePropAsync { get; set; }

    protected int _myVariable = 1;  // just an example
    public int MyProp { get; set; } = 2;  // just an example


    public MyAsyncBackgroundJob() {
    }

    public override Task ExecuteAsync(TExecuteArgs args) {
        return ExecutePropAsync(args);
    }
}

And how I'd like to use it:

var myVar = new MyAsyncBackgroundJob<string> {
    ExecutePropAsync = (string s) => {
        string output = null;

        // this example works:
        if (s == "example") output = s + s;

        // this example DOESN'T works as compiler says _myVariable not found:
        if (_myVariable == 1) output = s + s;

        // this example DOESN'T works as compiler says MyProp not found
        if (MyProp == 1) output = s + s;

        return Task.FromResult(output);
    }
};

// Note: in the real life ExecuteAsync(TExecuteArgs args) is called automatically by the framework
await myVar.ExecutePropAsync("example");
3

There are 3 answers

0
shingo On

Firstly you are wrong. The variable anonymous is not anonymous, it is strong typed as MyAsyncBackgroundJob<string>, but the lambda expression does indeed generate an anonymous type (more accurate: an unspeakable type).

Your problem is because you try to refer to the properties of MyAsyncBackgroundJob<string> within the scope of the lambda expression, Unlike within the scope of the class definition, there is no implicit this object in that scope, you must provide a reference of the object like obj.MyProp.

But when you use the object initializer like this: var obj = new MyAsyncBackgroundJob<string>{}, actually, this variable obj will only be assigned after the object initializer is completed, so you are not able to access it in the lambda expression. You can pre initialize it.

MyAsyncBackgroundJob<string> job = null;

job = new MyAsyncBackgroundJob<string> {
    ExecutePropAsync = (string s) => {
        string output = null;

        // this example works:
        if (s == "example") output = s + s;

        // this example DOESN'T works:
        //if (job._myVariable == 1) output = s + s;

        if (job.MyProp == 1) output = s + s;

        return Task.FromResult(output);
    }
};

However, no matter what, job._myVariable won't work, because you cannot access protected fields/properties outside the scope of the class definition.

0
Sweeper On

You can add an extra parameter to ExecutePropAsync, so that you can pass this when calling it. Then at the use site, you can have access to this via that parameter.

public Func<MyAsyncBackgroundJob<TExecuteArgs>, TExecuteArgs, Task> ExecutePropAsync { get; init; }

public Task ExecuteAsync(TExecuteArgs args) {
    return ExecutePropAsync(this, args);
}

Now this compiles:

var job = new MyAsyncBackgroundJob<string> {
    ExecutePropAsync = (self, s) => {
        string output = null;
        if (s == "example") output = s + s;
        if (self.MyProp == 1) output = s + s;

        return Task.FromResult(output);
    }
};

However, this approach does not allow you to access protected members. After all, the lambda you assign to ExecutePropAsync is not in MyAsyncBackgroundJob, or any of its subclasses.

While you can also pass the protected properties you need as parameters of the lambda in the same way (perhaps wrap them in another class first, if there are many), I personally would just create a subclass of MyAsyncBackgroundJob at this point, which is more maintainable. Otherwise, you'd have to update the lambda parameters/properties in the wrapper class every time you add a protected member in MyAsyncBackgroundJob.

1
Michał Turczyn On

I would suggest changing signature of ExecutePropAsync to accept two additional ints, which you can then pass when executing the method inside MyAsyncBackgroundJob<TExecuteArgs> class.

This way, you will be able to pass _myVariable and MyProp directly to method through parameters. And having parameters in the method, let's you use them inside it :)

Here's slightly refactored example of yours.

internal static class testarea
{
    internal static void Test()
    {
        var anonymous = new MyAsyncBackgroundJob<string>
        {
            ExecutePropAsync = (string s, int myVariable, int myProp) => {
                string output = null;

                if (s == "example") output = s + s;

                if (myVariable == 1) output = s + s;

                if (myProp == 1) output = s + s;

                return Task.FromResult(output);
            }
        };
    }
}

public class MyAsyncBackgroundJob<TExecuteArgs>
{

    public Func<TExecuteArgs, int, int, Task> ExecutePropAsync { get; set; }

    protected int _myVariable = 1;  // just an example
    public int MyProp { get; set; } = 2;  // just an example


    public MyAsyncBackgroundJob()
    {
    }

    public Task ExecuteAsync(TExecuteArgs args)
    {
        return ExecutePropAsync(args, _myVariable, MyProp);
    }
}