2013-07-29 10 views
9

ho scritto cose del genere nei miei implementazioni:Come vengono gestite le variabili locali quando si fa riferimento in un altro ambito?

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

Tuttavia, non so esattamente cosa sta succedendo qui ...

Forse tutto ciò è stato "copiato" per il delegato. O forse, come i tipi di riferimento, tutti i tipi di valore avranno una copia associata al delegato Task finché non esiste?

Sto solo cercando di capire cosa esattamente succede nelle ultime versioni C# per le questioni di prestazioni.

+0

Il modo migliore per comprenderlo è l'analisi del risultato della compilazione. Puoi decompilarlo e investigare. –

+1

Il codice decomposto è stato pubblicato da Marc :) –

risposta

7

Ottima domanda; variabili catturate e contesti di chiusura. Decompilazione dimostra che il compilatore corrente crea oggetti contesto cattura qui:

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

Se il vostro obiettivo è quello di ridurre al minimo gli oggetti di contesto, è possibile eseguire le chiusure manualmente:

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

si può anche evitare creazione delegato per-compito memorizzando una sola delegato che passa nello stato separatamente:

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

o (CLE 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

Il compilatore genera quei tipi solo per abbinare la "dimensione" di ogni contesto, giusto? – cvsguimaraes

+0

@sMember definisce "size"? le variabili ** dichiarate ** (sottolineatura nota) in un ambito diverso ottengono un diverso contesto di cattura –

+0

Ah, ora ho capito ... Sei in grado di ottenere anche il delegato generato? Voglio dire, 'class2. b__0' è ** identico ** al delegato originale ma utilizzando i valori dei contesti? – cvsguimaraes

1

Il termine tecnico per questo è un "chiusura": una funzione legata all'ambiente in cui è dichiarata.

La funzione (il delegato Attività anonima in questo caso), è associata all'ambiente della funzione genitore e ha accesso alle variabili del suo genitore, come se fossero proprie.

una spiegazione più completa si possono trovare in questo eccellente blog post, ma qui è un semplice esempio:

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 
} 

Dietro le quinte il compilatore C# creerà una nuova classe per contenere il contesto di chiusura (la variabile someValue in questo esempio) e rende al delegato anonimo un metodo di istanza di questa classe.

1

si sta parlando chiusure. Controlla questo article per scoprire cosa sta succedendo sotto la copertina.

Problemi correlati