2013-07-29 16 views
9

Pisałem takie rzeczy w moich realizacjach:W jaki sposób obsługiwane są zmienne lokalne w przypadku odwołania w innym zakresie?

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

Jednak nie wiem dokładnie, co się tutaj dzieje ...

Może wszystko było „skopiowane” do delegata. A może, podobnie jak typy referencyjne, wszystkie typy wartości będą miały kopię powiązaną z delegatem Task, dopóki nie będzie istnieć?

Po prostu próbuję zrozumieć, co się dzieje z dokładnie w najnowszych wersjach C# pod kątem wydajności.

+0

Najlepszym sposobem zrozumienia jest zbadanie wyniku kompilacji. Możesz go zdekompilować i zbadać. –

+1

Kod dekompilowany został opublikowany przez Marc :) –

Odpowiedz

7

Doskonałe pytanie; przechwycone zmienne i konteksty zamknięcia. Dekodowanie to pokazuje, że obecny kompilator tworzy kontekst przechwytywania obiektów tutaj:

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

Jeśli twoim celem jest zminimalizowanie obiektów kontekście można wykonać zamknięcia ręcznie:

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

Można również unikać tworem pełnomocnik per zadanie poprzez buforowanie jeden pełnomocnik, która przechodzi w stan oddzielnie:

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

lub (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

Kompilator generuje te typy, aby dopasować "rozmiar" każdego kontekstu, prawda? – cvsguimaraes

+0

@sMember zdefiniować "rozmiar"? zmienne ** zadeklarowane ** (uwaga) w innym zakresie uzyskają inny kontekst przechwytywania –

+0

Ah nevermind, teraz mam to ... Czy jesteś w stanie uzyskać również wygenerowanego delegata? Mam na myśli "class2. b__0' jest ** identyczny ** do oryginalnego uczestnika, ale używając wartości z kontekstów? – cvsguimaraes

1

Terminem technicznym jest "zamknięcie": funkcja związana ze środowiskiem, w którym jest zadeklarowana.

Funkcja (w tym przypadku anonimowy delegat zadania) jest powiązana ze środowiskiem funkcji nadrzędnej i ma dostęp do zmiennych macierzystych, tak jakby były one własne.

Pełniejsze wyjaśnienie można znaleźć w tej znakomitej blog post, ale oto prosty przykład:

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 
} 

Za kulisami C# kompilator utworzy nową klasę trzymać kontekst zamykający (zmienna someValue w ten przykład) i powoduje, że anonimowy delegat ma metodę instancji tej klasy.

1

mówisz zamknięć. Sprawdź to article, aby dowiedzieć się, co się dzieje pod osłoną.

Powiązane problemy