2009-11-06 5 views
34

ho il seguente codice:ReSharper Attenzione - L'accesso alla chiusura Modified

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

noti che account.AccountStatus è un enum di tipo ACCOUNTSTATUS. Sulla seconda riga, ReSharper mi sta dando l'avviso "Accesso a chiusura modificata" per acctStatus. Quando faccio l'operazione consigliata, Copia variabile locale, esso modifica il codice di seguito:

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

Perché è questo migliore o preferibile a quello che avevo in origine?

EDIT

Raccomanda inoltre Wrap variabile locale in ordine, che produce:

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

Questo sembra decisamente strambo per me.

+0

Controllare questo SO Domanda e risposta accettata, potrebbe essere utile. http://stackoverflow.com/questions/235455/access-to-modified-closure – Chuck

risposta

34

Il motivo dell'avviso è che all'interno di un ciclo si potrebbe accedere a una variabile che sta cambiando. Tuttavia, la "correzione" in realtà non fa nulla per te in questo contesto non ciclico.

Immaginate di avere un ciclo FOR e il if era al suo interno e la dichiarazione di stringa era esterna. In tal caso, l'errore identifica correttamente il problema di prendere un riferimento a qualcosa di instabile.

Un esempio di ciò che non si vuole:

string acctStatus 

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

Il problema è che la chiusura catturerà un riferimento a acctStatus, ma ogni iterazione del ciclo cambierà quel valore. In che caso sarebbe meglio:

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

Come contesto della variabile è il ciclo, una nuova istanza verrà creato ogni volta perché abbiamo spostato variabile all'interno del contesto locale (il ciclo).

La segnalazione sembra un errore nell'analisi di quel codice da parte di Resharper. Tuttavia, in molti casi questo è un problema valido (come il primo esempio, in cui il riferimento sta cambiando nonostante la sua cattura in una chiusura).

La mia regola generale è, in caso di dubbio, fare un locale.

Ecco un esempio reale mondo mi è stato morso da:

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

Nota che catturo il tipo NodeType locali, nota nodeType, e HistoryItem crumb.ItemGuid, non briciole [i] .ItemGuid. Ciò garantisce che la mia chiusura non abbia riferimenti a elementi che cambieranno.

Prima di utilizzare i locali, gli eventi si attivavano con i valori correnti, non con i valori catturati che mi aspettavo.

+0

Ha molto senso, grazie! –

+1

"Il problema è che la chiusura catturerà un riferimento a acctStatus, ma ogni iterazione del ciclo cambierà quel valore" - in realtà sarà sempre 'account.AccountStatus [...]. ToString()' ogni volta che il predicato 'Any' lo valuta (poiché 'Any' scorre sopra l'enumerabile prima di tornare), quindi non sono sicuro che sia un buon esempio. Credo che ciò che hai proposto come migliore avrà lo stesso comportamento. –

+1

Questo non sarebbe corretto. Il motivo della differenza è che nel primo esempio viene allocata una sola variabile: acctStatus. Resharper è preoccupato che questa variabile cambierà prima che Any() venga effettivamente valutato. Il mio suggerimento non cambia affatto l'azione del codice (Any() viene valutato prima che avvenga qualsiasi modifica) ma rende esplicito che vogliamo un'istanza distinta della variabile acctStatus per ogni iterazione del ciclo. Se noti il ​​mio esempio successivo, il bug Resharper è preoccupato per i trigger in realtà perché * non * valuta immediatamente il lambda (meno i locali). – Godeke