2009-11-06 10 views
34

je le code suivant:ReSharper Avertissement - L'accès à la fermeture modifiée

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

Notez que account.AccountStatus est un ENUM de type accountstatus. Sur la deuxième ligne, ReSharper me donne l'avertissement "Access to Modified Closure" pour acctStatus. Quand je fais l'opération recommandée, Copie à la variable locale, il modifie le code à ce qui suit:

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

Pourquoi est-ce mieux ou préférable à ce que j'avais à l'origine?

EDIT

Il recommande également Wrap variable locale dans le tableau, qui produit:

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

Cela me semble carrément farfelue à.

+0

Cochez cette case Question et réponse acceptée, pourrait être utile. http://stackoverflow.com/questions/235455/access-to-modified-closure – Chuck

Répondre

34

La raison de l'avertissement est qu'à l'intérieur d'une boucle, vous pouvez accéder à une variable en cours de modification. Cependant, le "correctif" ne fait vraiment rien pour vous dans ce contexte sans boucle. Imaginez que vous aviez une boucle FOR et que le si était à l'intérieur et que la déclaration de chaîne était à l'extérieur de celle-ci. Dans ce cas, l'erreur identifierait correctement le problème de saisir une référence à quelque chose d'instable.

Un exemple de ce que vous ne voulez pas:

string acctStatus 

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

Le problème est que la fermeture saisira une référence à acctStatus, mais chaque itération de la boucle va changer cette valeur. Dans que cas, il serait préférable:

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

Comme le contexte de la variable est la boucle, une nouvelle instance sera créé à chaque fois parce que nous avons déplacé la variable à l'intérieur du contexte local (la boucle).

La recommandation ressemble à un bogue dans l'analyse de Resharper de ce code. Cependant, dans de nombreux cas, ceci est une préoccupation valable (comme dans le premier exemple, où la référence change malgré sa capture dans une fermeture).

Ma règle générale est, en cas de doute, de faire un local.

Voici un exemple du monde réel j'ai été mordu par:

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

Notez que je capture le type NodeType locale, note nodeType et HistoryItem crumb.ItemGuid, pas des miettes [i] .ItemGuid. Cela garantit que ma fermeture n'aura pas de références aux éléments qui vont changer. Avant d'utiliser les variables locales, les événements se déclenchent avec les valeurs actuelles et non avec les valeurs capturées que je m'attendais.

+0

Cela a beaucoup de sens, merci! –

+1

"Le problème est que la fermeture va saisir une référence à acctStatus, mais chaque itération de la boucle changera cette valeur" - en fait, elle sera toujours 'account.AccountStatus [...]. ToString()' à chaque fois que le prédicat 'Any' l'évalue (puisque 'Any' itère sur l'énumérable avant de revenir), donc je ne suis pas sûr que ce soit un bon exemple. Je crois que ce que vous avez proposé comme meilleur aura le même comportement. –

+1

Ce serait incorrect. La raison de la différence est que dans le premier exemple, une seule variable est allouée: acctStatus. Resharper craint que cette variable change avant que Any() ne soit réellement évalué. Ma suggestion ne change pas du tout l'action du code (Any() est évalué avant tout changement) mais rend explicite que nous voulons une instance distincte de la variable acctStatus pour chaque itération de la boucle. Si vous notez mon exemple plus tard, le bug Resharper s'inquiète des déclencheurs parce que je * n'évalue pas immédiatement le lambda (moins les locaux). – Godeke