Accès à une fermeture modifiée

316
string [] files = new string[2];
files[0] = "ThinkFarAhead.Example.Settings.Configuration_Local.xml";
files[1] = "ThinkFarAhead.Example.Settings.Configuration_Global.xml";

//Resharper complains this is an "access to modified closure"
for (int i = 0; i < files.Length; i++ )
{
    // Resharper disable AccessToModifiedClosure
    if(Array.Exists(Assembly.GetExecutingAssembly().GetManifestResourceNames(),
    delegate(string name) { return name.Equals(files[i]); }))
         return Assembly.GetExecutingAssembly().GetManifestResourceStream(files[i]);
    // ReSharper restore AccessToModifiedClosure
}

Ce qui précède semble fonctionner correctement, bien que ReSharper se plaint qu'il s'agit d'un "accès à une fermeture modifiée". Quelqu'un peut-il faire la lumière sur cette question?

(ce sujet a continué ici )

Vyas Bharghava
la source
6
Le lien est sorti, mais je l'ai trouvé sur WebArchive: web.archive.org/web/20150326104221/http://www.jarloo.com/…
Eric Wu

Réponses:

314

Dans ce cas, ce n'est pas grave, car vous exécutez réellement le délégué dans la boucle.

Si vous enregistrez le délégué et l'utilisez plus tard, cependant, vous constaterez que tous les délégués lèveraient des exceptions lorsqu'ils essaient d'accéder aux fichiers [i] - ils capturent la variable i plutôt que sa valeur au moment des délégués création.

En bref, c'est quelque chose dont vous devez être conscient en tant que piège potentiel , mais dans ce cas, cela ne vous fait pas de mal.

Voir le bas de cette page pour un exemple plus complexe où les résultats sont contre-intuitifs.

Jon Skeet
la source
29

Je sais que c'est une vieille question, mais j'ai récemment étudié les fermetures et j'ai pensé qu'un exemple de code pourrait être utile. Dans les coulisses, le compilateur génère une classe qui représente une fermeture lexicale pour votre appel de fonction. Cela ressemble probablement à quelque chose comme:

private sealed class Closure
{
    public string[] files;
    public int i;

    public bool YourAnonymousMethod(string name)
    {
        return name.Equals(this.files[this.i]);
    }
}

Comme mentionné ci-dessus, votre fonction fonctionne car les prédicats sont appelés immédiatement après la création. Le compilateur va générer quelque chose comme:

private string Works()
{
    var closure = new Closure();

    closure.files = new string[3];
    closure.files[0] = "notfoo";
    closure.files[1] = "bar";
    closure.files[2] = "notbaz";

    var arrayToSearch = new string[] { "foo", "bar", "baz" };

    //this works, because the predicates are being executed during the loop
    for (closure.i = 0; closure.i < closure.files.Length; closure.i++)
    {
        if (Array.Exists(arrayToSearch, closure.YourAnonymousMethod))
            return closure.files[closure.i];
    }

    return null;
}

D'un autre côté, si vous deviez stocker puis invoquer plus tard les prédicats, vous verriez que chaque appel unique aux prédicats appellerait vraiment la même méthode sur la même instance de la classe de fermeture et utiliserait donc la même valeur pour je.

gerrard00
la source
4

"fichiers" est une variable externe capturée car elle a été capturée par la fonction de délégué anonyme. Sa durée de vie est prolongée par la fonction de délégué anonyme.

Variables externes capturées Lorsqu'une variable externe est référencée par une fonction anonyme, la variable externe est censée avoir été capturée par la fonction anonyme. Habituellement, la durée de vie d'une variable locale est limitée à l'exécution du bloc ou de l'instruction à laquelle elle est associée (variables locales). Cependant, la durée de vie d'une variable externe capturée est prolongée au moins jusqu'à ce que l'arborescence de délégué ou d'expression créée à partir de la fonction anonyme devienne éligible pour le garbage collection.

Variables externes sur MSDN

Lorsqu'une variable locale ou un paramètre de valeur est capturé par une fonction anonyme, la variable ou le paramètre local n'est plus considéré comme une variable fixe (variables fixes et mobiles), mais est plutôt considéré comme une variable mobile. Ainsi, tout code dangereux qui prend l'adresse d'une variable externe capturée doit d'abord utiliser l'instruction fixed pour corriger la variable. Notez que contrairement à une variable non capturée, une variable locale capturée peut être simultanément exposée à plusieurs threads d'exécution.

chris hu
la source