Acesso ao fechamento 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
}

O exposto acima parece funcionar bem, embora o ReSharper reclame que este é "acesso ao fechamento modificado". Alguém pode esclarecer isto?

(este tópico continua aqui )

Vyas Bharghava
fonte
6
O link está esgotado, mas eu o encontrei no WebArchive: web.archive.org/web/20150326104221/http://www.jarloo.com/…
Eric Wu

Respostas:

314

Nesse caso, tudo bem, pois você está realmente executando o delegado dentro do loop.

Se você estivesse salvando o delegado e o usasse mais tarde, no entanto, descobriria que todos os delegados lançariam exceções ao tentar acessar os arquivos [i] - eles estão capturando a variável em i vez de seu valor no momento dos delegados criação.

Em suma, é algo para estar ciente de uma armadilha em potencial , mas, neste caso, não a machuca.

Veja o final desta página para um exemplo mais complexo, onde os resultados são contra-intuitivos.

Jon Skeet
fonte
29

Sei que essa é uma pergunta antiga, mas recentemente estudei fechamentos e achei que um exemplo de código poderia ser útil. Nos bastidores, o compilador está gerando uma classe que representa um fechamento lexical para sua chamada de função. Provavelmente se parece com:

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

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

Como mencionado acima, sua função funciona porque os predicados são chamados imediatamente após a criação. O compilador irá gerar 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 outro lado, se você armazenasse e depois invocasse os predicados, veria que todas as chamadas para os predicados realmente chamariam o mesmo método na mesma instância da classe de fechamento e, portanto, usariam o mesmo valor para Eu.

gerrard00
fonte
4

"files" é uma variável externa capturada porque foi capturada pela função delegada anônima. Seu tempo de vida é estendido pela função de delegado anônimo.

Variáveis ​​externas capturadas Quando uma variável externa é referenciada por uma função anônima, diz-se que a variável externa foi capturada pela função anônima. Normalmente, o tempo de vida de uma variável local é limitado à execução do bloco ou instrução à qual está associada (variáveis ​​locais). No entanto, a vida útil de uma variável externa capturada é estendida pelo menos até que o delegado ou a árvore de expressão criada a partir da função anônima se torne elegível para coleta de lixo.

Variáveis ​​externas no MSDN

Quando uma variável local ou um parâmetro de valor é capturado por uma função anônima, a variável ou parâmetro local não é mais considerado uma variável fixa (variáveis ​​fixas e móveis), mas é considerado uma variável móvel. Portanto, qualquer código não seguro que use o endereço de uma variável externa capturada deve primeiro usar a instrução fixa para corrigir a variável. Observe que, diferentemente de uma variável não capturada, uma variável local capturada pode ser exposta simultaneamente a vários encadeamentos de execução.

chris hu
fonte