Zgaduję, że może być, aby uniknąć sytuacji wyścigu, gdzie jeśli masz:
dictionary[i] = dictionary[i] + 1
To nie atomowy. dictionary
przypisany do może zmienić po masz wartość i inkrementowane.
Wyobraźmy sobie ten kod:
public Dictionary<int, int> dictionary = new Dictionary<int, int>();
public void Increment()
{
int newValue = dictionary[0] + 1;
//meanwhile, right now in another thread: dictionary = new Dictionary<int, int>();
dictionary[0] = newValue; //at this point, "dictionary" is actually pointing to a whole new instance
}
z lokalnym zmiennej przypisania mają, to wygląda bardziej jak tego uniknąć warunek:
public void IncrementFix()
{
var dictionary2 = dictionary;
//in another thread: dictionary = new Dictionary<int, int>();
//this is OK, dictionary2 is still pointing to the ORIGINAL dictionary
int newValue = dictionary2[0] + 1;
dictionary2[0] = newValue;
}
pamiętać, że nie w pełni spełniają wszystkie nici - bezpieczne wymagania. Na przykład w tym przypadku zaczniemy zwiększać wartość, ale odwołanie do klasy dictionary
zmieniło się na zupełnie nową instancję. Ale jeśli potrzebujesz wyższego poziomu bezpieczeństwa wątku, musisz zaimplementować własną agresywną synchronizację/blokowanie, które zwykle wykracza poza zakres optymalizacji kompilatora. Jednak ten, z tego, co wiem, nie dodaje żadnego dużego trafienia (jeśli w ogóle) do przetwarzania i unika tego warunku. Może się tak zdarzyć, gdy dictionary
jest własnością , a nie takim, jakim jest to pole w twoim przykładzie, w którym to przypadku z pewnością nie byłoby optymalizacją dwukrotne rozwiązanie właściwości getter. (Czy przypadkiem nie jest to rzeczywisty kod korzystający z właściwości słownika, a nie z pola, które opublikowałeś?)
EDIT: No, za pomocą prostego sposobu:
public void IncrementDictionary()
{
dictionary[0]++;
}
IL Zgłaszane od LINQPad jest:
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldfld UserQuery.dictionary
IL_0007: dup
IL_0008: stloc.0
IL_0009: ldc.i4.0
IL_000A: ldloc.0
IL_000B: ldc.i4.0
IL_000C: callvirt System.Collections.Generic.Dictionary<System.Int32,System.Int32>.get_Item
IL_0011: ldc.i4.1
IL_0012: add
IL_0013: callvirt System.Collections.Generic.Dictionary<System.Int32,System.Int32>.set_Item
IL_0018: nop
IL_0019: ret
Nie jestem do końca pewien (nie jestem wiz IL) , ale myślę, że wywołanie dup
powoduje podwojenie tego samego słownika na stosie, więc niezależnie od tego, zarówno wywołania get, jak i set będą wskazywać na ten sam słownik. Być może właśnie w ten sposób ILSpy przedstawia go jako kod C# (jest mniej więcej taki sam, co zachowanie). Myślę. Proszę, popraw mnie, jeśli się mylę, ponieważ, tak jak powiedziałem, nie znam jeszcze IL, jak na dłoni.
EDYCJA: Musisz uruchomić, ale ostateczny sens to: ++
i +=
nie są operacjami atomowymi i są w rzeczywistości dużo bardziej skomplikowane w wykonywanych instrukcjach niż te przedstawione w C#. W związku z tym, aby upewnić się, że każdy z kroków get/increment/set zostanie wykonany na tej samej instancji słownikowej (jak można się tego spodziewać po kodzie C#), lokalna referencja do słownika jest wykonywana w celu uniknięcia działania pola " "dwukrotnie wykonaj operację, która może spowodować wskazanie nowej instancji. W jaki sposób ILSpy pokazuje, że wszystko, co dotyczy pracy z indeksowaną operacją + =, zależy od niej.
Ważna uwaga: nie utworzono drugiego słownika. Zamiast tego jest brane pod uwagę drugie odniesienie do istniejącego słownika. – Jon
@Jon: W porządku, mój błąd. –
Opublikowany przez Ciebie plik ILSpy nie jest dokładnym odzwierciedleniem tego, co dzieje się bezpośrednio w kodzie CIL. Możliwe, że jest to błąd lub niedoskonałość dekompilatora CIL-to-C#. Czy możesz opublikować CIL, abyśmy byli pewni? – Dai