Acceso a cierre modificado

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
}

Lo anterior parece funcionar bien, aunque ReSharper se queja de que esto es "acceso al cierre modificado". ¿Alguien puede arrojar luz sobre esto?

(este tema continuó aquí )

Vyas Bharghava
fuente
66
El enlace está fuera, pero lo encontré en WebArchive: web.archive.org/web/20150326104221/http://www.jarloo.com/…
Eric Wu

Respuestas:

314

En este caso, está bien, ya que en realidad está ejecutando el delegado dentro del bucle.

Sin embargo, si guardara el delegado y lo usara más tarde, descubriría que todos los delegados arrojarían excepciones al intentar acceder a los archivos [i]: están capturando la variable en i lugar de su valor en el momento de los delegados. creación.

En resumen, es algo a tener en cuenta como una trampa potencial , pero en este caso no te hace daño.

Consulte la parte inferior de esta página para ver un ejemplo más complejo en el que los resultados son contradictorios.

Jon Skeet
fuente
29

Sé que esta es una vieja pregunta, pero recientemente he estado estudiando cierres y pensé que una muestra de código podría ser útil. Detrás de escena, el compilador está generando una clase que representa un cierre léxico para su llamada a la función. Probablemente se parece a algo como:

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

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

Como se mencionó anteriormente, su función funciona porque los predicados se invocan inmediatamente después de la creación. El compilador generará algo como:

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

Por otro lado, si tuviera que almacenar y luego invocar los predicados, vería que cada llamada a los predicados realmente llamaría al mismo método en la misma instancia de la clase de cierre y, por lo tanto, usaría el mismo valor para yo.

gerrard00
fuente
4

"archivos" es una variable externa capturada porque ha sido capturada por la función de delegado anónimo. Su vida útil se extiende por la función de delegado anónimo.

Variables externas capturadas Cuando una función anónima hace referencia a una variable externa, se dice que la variable externa ha sido capturada por la función anónima. Por lo general, la vida útil de una variable local se limita a la ejecución del bloque o declaración con la que está asociada (variables locales). Sin embargo, la vida útil de una variable externa capturada se extiende al menos hasta que el árbol delegado o de expresión creado a partir de la función anónima sea elegible para la recolección de basura.

Variables externas en MSDN

Cuando una función anónima captura una variable local o un parámetro de valor, la variable o parámetro local ya no se considera una variable fija (variables fijas y móviles), sino que se considera una variable móvil. Por lo tanto, cualquier código inseguro que tome la dirección de una variable externa capturada primero debe usar la instrucción fija para corregir la variable. Tenga en cuenta que, a diferencia de una variable no capturada, una variable local capturada puede exponerse simultáneamente a múltiples hilos de ejecución.

chris hu
fuente