2009-11-06 5 views
34

Mam następujący kod:ReSharper Ostrzeżenie - Dostęp do zmodyfikowanym zamknięciem

string acctStatus = account.AccountStatus.ToString(); 
if (!SettableStatuses().Any(status => status == acctStatus)) 
    acctStatus = ACCOUNTSTATUS.Pending.ToString(); 

Zauważ, że account.AccountStatus jest enum typu ACCOUNTSTATUS. W drugim wierszu, ReSharper daje mi ostrzeżenie "Dostęp do modyfikacji" dla acctStatus. Kiedy zrobić zalecaną operację Kopiuj do zmiennej lokalnej, modyfikuje kod do następujących:

string acctStatus = realAccount.AccountStatus.ToString(); 
string s = acctStatus; 
if (!SettableStatuses().Any(status => status == s)) 
    acctStatus = ACCOUNTSTATUS.Pending.ToString(); 

Dlaczego to lepiej lub korzystnie co miałem pierwotnie jest?

EDIT

Zaleca również Wrap lokalnej zmiennej w tablicy, która produkuje:

string[] acctStatus = {realAccount.AccountStatus.ToString()}; 
if (!SettableStatuses().Any(status => status == acctStatus[0])) 
    acctStatus[0] = ACCOUNTSTATUS.Pending.ToString(); 

Wydaje mi wręcz głupi.

+0

Sprawdź to Pytanie i zaakceptowana odpowiedź mogą okazać się pomocne. http://stackoverflow.com/questions/235455/access-to-modified-closure – Chuck

Odpowiedz

34

Powodem ostrzeżenia jest to, że wewnątrz pętli można uzyskać dostęp do zmiennej, która się zmienia. Jednak "poprawka" tak naprawdę nie robi nic dla ciebie w tym kontekście bez pętli.

Wyobraź sobie, że masz pętlę FOR i jeśli była w środku, a deklaracja łańcucha znajdowała się poza nią. W takim przypadku błąd byłby prawidłowym identyfikowaniem problemu przechwytywania odniesienia do czegoś niestabilnego.

Przykładem tego, czego nie chcą:

string acctStatus 

foreach(...) 
{ 
    acctStatus = account.AccountStatus[...].ToString(); 
    if (!SettableStatuses().Any(status => status == acctStatus)) 
     acctStatus = ACCOUNTSTATUS.Pending.ToString(); 
} 

Problem polega na tym, że zamknięcie przechwyci odniesienie do acctStatus, ale każdej iteracji pętli zmienia tę wartość. W że przypadku lepiej byłoby:

foreach(...) 
{ 
    string acctStatus = account.AccountStatus[...].ToString(); 
    if (!SettableStatuses().Any(status => status == acctStatus)) 
     acctStatus = ACCOUNTSTATUS.Pending.ToString(); 
} 

W kontekście tej zmiennej jest pętla, nowa instancja będzie tworzone za każdym razem, ponieważ zostały przeniesione zmienną wewnątrz kontekstu lokalnego (do pętli).

Zalecenie brzmi jak błąd w analizie tego kodu przez Resharpera. Jednak w wielu przypadkach jest to ważny problem (np. Pierwszy przykład, w którym odniesienie się zmienia pomimo przechwytywania w zamknięciu).

Moja zasada jest taka, że ​​gdy masz wątpliwości, zrób coś lokalnego.

Oto prawdziwy przykład świat byłem ugryziony przez:

 menu.MenuItems.Clear(); 
     HistoryItem[] crumbs = policyTree.Crumbs.GetCrumbs(nodeType); 

     for (int i = crumbs.Length - 1; i > -1; i--) //Run through items backwards. 
     { 
      HistoryItem crumb = crumbs[i]; 
      NodeType type = nodeType; //Local to capture type. 
      MenuItem menuItem = new MenuItem(crumb.MenuText); 
      menuItem.Click += (s, e) => NavigateToRecord(crumb.ItemGuid, type); 
      menu.MenuItems.Add(menuItem); 
     } 

pamiętać, że przechwytywanie typ NODETYPE lokalny, zwróć uwagę NODETYPE i HistoryItem crumb.ItemGuid, a nie okruchów [i] .ItemGuid. Dzięki temu moje zamknięcie nie będzie zawierało odniesień do elementów, które ulegną zmianie.

Przed użyciem locals zdarzenia wywołałyby bieżące wartości, a nie oczekiwane wartości przechwycone.

+0

Ma wiele sensu, dzięki! –

+1

"Problem polega na tym, że zamknięcie pobierze odniesienie do acctStatus, ale każda iteracja pętli zmieni tę wartość" - tak naprawdę zawsze będzie to "account.AccountStatus [...] ToString()", gdy tylko predykat "Any" ocenia to (ponieważ 'Any' iteruje nad przeliczalnym przed powrotem), więc nie jestem pewien, czy to dobry przykład. Wierzę, że to, co zaproponowałeś jako lepsze, będzie miało takie samo zachowanie. –

+1

To byłoby nieprawidłowe. Przyczyną tej różnicy jest to, że w pierwszym przykładzie przydzielana jest tylko jedna zmienna: acctStatus. Resharper obawia się, że zmienna ta ulegnie zmianie, zanim funkcja Any() zostanie faktycznie oceniona. Moja sugestia w ogóle nie zmienia działania kodu (Any() jest oceniany przed każdą zmianą), ale wyraźnie zaznacza, że ​​chcemy odrębnej instancji zmiennej acctStatus dla każdej iteracji pętli. Jeśli zauważysz mój późniejszy przykład, błąd Resharpera martwi się o rzeczywiście wyzwalacze, ponieważ * nie * natychmiastowo oceniam lambda (bez lokalnych). – Godeke

Powiązane problemy