2013-07-29 4 views
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#バージョンでパフォーマンスの問題が発生していることを理解しようとしています。

+0

これを理解する最も良い方法は、コンパイル結果の調査です。それを逆コンパイルして調べることができます。 –

+1

コンパイルされたコードはMarcによって公開されています:) –

答えて

7

優秀な質問;キャプチャされた変数とクロージャのコンテキスト。逆コンパイル、それは現在のコンパイラがキャプチャコンテキストがここにオブジェクトを作成していることを示しています。

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

またはaner、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); 
    } 
} 
+0

コンパイラは各コンテキストの「サイズ」と一致するようにこれらの型を生成します。 – cvsguimaraes

+0

@sMemberは「サイズ」を定義しますか?変数**宣言**別のスコープ内の注記(強調)他のキャプチャコンテキストを取得 –

+0

ああ、ここで私はそれを得た...あなたは生成されたデリゲートも得ることができますか?つまり、class2です。 b__0'は元のデリゲートに**同一**ですが、コンテキストの値を使用していますか? – cvsguimaraes

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

1

あなたは閉鎖について話しています。 カバーの下に起こっていることについてはarticleをご覧ください。

関連する問題