9

私は自分の実装でこのようなことを書いてきました:

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#バージョンで正確に何が起こるかを理解しようとしています。

4

3 に答える 3

8

素晴らしい質問です。キャプチャされた変数とクロージャー コンテキスト。これを逆コンパイルすると、現在のコンパイラが次の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);
    }
}
于 2013-07-29T08:45:57.410 に答える
1

これの専門用語は「クロージャー」です。宣言された環境にバインドされた関数です。

関数 (この場合は匿名の Task デリゲート) は、親関数の環境にバインドされ、親関数の変数にアクセスできます。

より完全な説明は、この優れたブログ投稿にありますが、簡単な例を次に示します。

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この例では変数) を保持する新しいクラスを作成し、匿名デリゲートをこのクラスのインスタンス メソッドにします。

于 2013-07-29T08:45:36.063 に答える
1

あなたは閉鎖について話している。カバーの下で何が起こっているかについては、この記事をご覧ください。

于 2013-07-29T08:50:12.060 に答える