在另一个范围内引用时如何处理局部变量?

时间:2013-07-29 08:35:45

标签: c#

我在实施中一直在写这样的东西:

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();
}

但是,我不知道到底发生了什么......

也许所有内容都被“复制”给了代理人。或者,与引用类型一样,所有值类型都有一个与Task委托关联的副本,直到它存在?

我只是想了解在最新的C#版本中出现完全的性能问题。

3 个答案:

答案 0 :(得分:7)

优秀的问题;捕获变量和闭包上下文。反编译显示当前编译器在此处创建 2 捕获上下文对象:

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;
}

如果您的目标是最小化上下文对象,则可以手动执行闭包:

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);
    }
}

您还可以通过缓存单独传递状态的单个委托来避免每个任务创建委托:

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);
    }
}

或(清洁,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);
    }
}

答案 1 :(得分:1)

这个技术术语是“闭包”:一个绑定到声明它的环境的函数。

该函数(在本例中为匿名任务委托)绑定到父函数的环境,并且可以访问其父变量,就像它们自己的变量一样。

在这个优秀的blog post中可以找到更全面的解释,但这是一个简单的例子:

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
}

在幕后,C#编译器将创建一个新类来保存闭包上下文(在此示例中为变量someValue),并使匿名委托成为此类的实例方法。

答案 2 :(得分:1)

您正在谈论闭包。 请查看此article以了解封面下发生的情况。