简体   繁体   中英

How local variables are handled when referenced in another scope?

I've been writing things like this in my implementations:

public void SomeMethod(int someValue, List<int> someValues)
{
  Task generatedTask = null;

  {
    int anotherValue = 2;
    object valuesRef = someValues;
    generatedTask = new Task(delegate{
      anotherValue += someValue + GetSum(valuesRef);
      Console.WriteLine(anotherValue);
    });
  }

  generatedTask.Start();
}

However, I don't know exactly what's happening here...

Maybe everything was "copied" to the delegate. Or maybe, like reference types, all value types will have a copy associated to the Task delegate until it exists?

I'm just trying to understand what exactly happens in latest C# versions for performance matters.

Excellent question; captured variables and closure contexts. Decompiling it shows that the current compiler creates 2 capture context objects here:

public void SomeMethod(int someValue, List<int> someValues)
{
    Task task;
    <>c__DisplayClass3 class2; // <== compiler generated type; unpronounceable
    <>c__DisplayClass1 class3; // <== compiler generated type; unpronounceable
    class3 = new <>c__DisplayClass1(); // outer-scope context
    class3.someValue = someValue;
    task = null;
    class2 = new <>c__DisplayClass3(); // <== inner-scope context
    class2.CS$<>8__locals2 = class3; // <== bind the contexts
    class2.anotherValue = 2;
    class2.valuesRef = someValues;
    task = new Task(new Action(class2.<SomeMethod>b__0));
    task.Start();
    return;
}

If your objective is to minimise context objects, you could perform the closures manually:

public void SomeMethod2(int someValue, List<int> someValues)
{
    Task generatedTask = null;
    {
        var ctx = new MyCaptureContext();
        ctx.anotherValue = 2;
        ctx.valuesRef = someValues;
        ctx.someValue = someValue;
        generatedTask = new Task(ctx.SomeMethod);
    }

    generatedTask.Start();
}

class MyCaptureContext
{
    // kept as fields to mimic the compiler
    public int anotherValue;
    public int someValue;
    public object valuesRef;
    public void SomeMethod()
    {
        anotherValue += someValue + GetSum(valuesRef);
        Console.WriteLine(anotherValue);
    }
}

You can also avoid a delegate creation per-task by caching a single delegate that passes in the state separately:

public void SomeMethod(int someValue, List<int> someValues)
{
    Task generatedTask = null;
    {
        var ctx = new MyCaptureContext();
        ctx.anotherValue = 2;
        ctx.valuesRef = someValues;
        ctx.someValue = someValue;
        generatedTask = new Task(MyCaptureContext.SomeMethod, ctx);
    }

    generatedTask.Start();
}
class MyCaptureContext
{
    // kept as fields to mimic the compiler
    public int anotherValue;
    public int someValue;
    public object valuesRef;
    public static readonly Action<object> SomeMethod = SomeMethodImpl;
    private static void SomeMethodImpl(object state)
    {
        var ctx = (MyCaptureContext)state;
        ctx.anotherValue += ctx.someValue + GetSum(ctx.valuesRef);
        Console.WriteLine(ctx.anotherValue);
    }
}

or (cleaner, IMO):

public void SomeMethod(int someValue, List<int> someValues)
{
    Task generatedTask = null;
    {
        var ctx = new MyCaptureContext();
        ctx.anotherValue = 2;
        ctx.valuesRef = someValues;
        ctx.someValue = someValue;
        generatedTask = ctx.CreateTask();
    }

    generatedTask.Start();
}
class MyCaptureContext
{
    // kept as fields to mimic the compiler
    public int anotherValue;
    public int someValue;
    public object valuesRef;
    public Task CreateTask()
    {
        return new Task(someMethod, this);
    }
    private static readonly Action<object> someMethod = SomeMethod;
    private static void SomeMethod(object state)
    {
        var ctx = (MyCaptureContext)state;
        ctx.anotherValue += ctx.someValue + GetSum(ctx.valuesRef);
        Console.WriteLine(ctx.anotherValue);
    }
}

The technical term for this is a " closure ": a function bound to the environment in which it is declared.

The function (the anonymous Task delegate in this case), is bound to the parent function's environment, and has access to its parent's variables, as if they were it's own.

A fuller explanation can be found in this excellent blog post , but here's a simple example:

public void SomeMethod()
{
    Task generatedTask = null;

    {
        int someValue = 2;

        generatedTask = new Task(delegate{
            Console.WriteLine(someValue);
        });
    }

    someValue = 3;

    generatedTask.Start(); // Will write "3" to the console
}

Behind the scenes the C# compiler will create a new class to hold the closure context (the variable someValue in this example), and makes the anonymous delegate an instance method of this class.

You are talking about closures . Check this article to learn about what is happenning under the cover.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM