Definindo objetos como nulo / nada após o uso no .NET

187

Você deve definir todos os objetos para null( Nothingno VB.NET) depois de terminar com eles?

Eu entendo que no .NET é essencial descartar quaisquer instâncias de objetos que implementam a IDisposableinterface para liberar alguns recursos, embora o objeto ainda possa ser algo depois de ser descartado (daí a isDisposedpropriedade nos formulários), então presumo que ele ainda possa residir na memória ou pelo menos em parte?

Sei também que quando um objeto sai do escopo, ele é marcado para coleta pronto para a próxima passagem do coletor de lixo (embora isso possa levar tempo).

Portanto, com isso em mente, será possível configurá-lo para nullacelerar o sistema que libera a memória, uma vez que não é necessário descobrir que ele não está mais no escopo e existem efeitos colaterais ruins?

Os artigos da MSDN nunca fazem isso em exemplos e, atualmente, faço isso porque não vejo o dano. No entanto, me deparei com uma mistura de opiniões, para que quaisquer comentários sejam úteis.

John
fonte
4
+1 ótima pergunta. Alguém conhece uma circunstância sob a qual o compilador otimizará completamente a atribuição? ou seja, alguém olhou para o MSIL sob diferentes circunstâncias e observou a IL por definir um objeto como nulo (ou a falta dele).
Tim Medora

Respostas:

73

Karl está absolutamente correto, não há necessidade de definir objetos como nulos após o uso. Se um objeto for implementado IDisposable, certifique-se de ligar IDisposable.Dispose()quando terminar o objeto (envolvido em um try.. finallyou um using()bloco). Mas mesmo que você não se lembre de ligar Dispose(), o método finalizador do objeto deve estar chamando Dispose()por você.

Eu pensei que este era um bom tratamento:

Cavando em IDisposable

e isto

Entendendo IDisposable

Não faz sentido tentar adivinhar o GC e suas estratégias de gerenciamento porque é auto-ajustável e opaco. Houve uma boa discussão sobre o funcionamento interno de Jeffrey Richter no Dot Net Rocks aqui: Jeffrey Richter no modelo de memória do Windows e no livro CLR de Richters, via CLR via C #, o capítulo 20 tem um ótimo tratamento:

Kev
fonte
6
A regra de não definir nulo não é "rígida e rápida" ... se o objeto for colocado na pilha de objetos grandes (o tamanho é> 85K), ajudará o GC se você definir o objeto como nulo quando terminar usando isso.
Scott Dorman
Concordo em certa medida, mas, a menos que você esteja começando a sofrer pressão de memória, não vejo necessidade de 'otimizar prematuramente' definindo objetos como nulos após o uso.
Kev
21
Todo esse negócio de "não otimizar prematuramente" soa mais como "Prefira devagar e não se preocupe, porque as CPUs estão ficando mais rápidas e os aplicativos CRUD não precisam de velocidade de qualquer maneira". Pode ser apenas eu embora. :)
BobbyShaftoe
19
O que realmente significa é "O Garbage Collector é melhor no gerenciamento de memória do que você". Isso pode ser apenas comigo. :)
BobRodes
2
@BobbyShaftoe: Provavelmente é tão errado dizer "otimização prematura é ruim, sempre" quanto pular para o extremo oposto de "soa mais como 'prefira devagar'". Nenhum programador razoável diria também. É sobre nuances e ser inteligente sobre o que você está otimizando. Pessoalmente, eu me preocupo com a clareza do código e com o desempenho do TESTE REALMENTE, pois já vi muitas pessoas (inclusive eu quando era mais nova) gastando muito tempo criando o algoritmo "perfeito", apenas para economizar 0,1ms em 100.000 iterações, enquanto a legibilidade foi completamente filmada.
Brent Rittenhouse
36

Outro motivo para evitar definir objetos como nulos quando você terminar com eles é que ele pode realmente mantê-los vivos por mais tempo.

por exemplo

void foo()
{
    var someType = new SomeType();
    someType.DoSomething();
    // someType is now eligible for garbage collection         

    // ... rest of method not using 'someType' ...
}

permitirá que o objeto referido por someType seja submetido ao GC após a chamada para "DoSomething", mas

void foo()
{
    var someType = new SomeType();
    someType.DoSomething();
    // someType is NOT eligible for garbage collection yet
    // because that variable is used at the end of the method         

    // ... rest of method not using 'someType' ...
    someType = null;
}

às vezes, pode manter o objeto vivo até o final do método. O JIT geralmente otimiza a atribuição para null , portanto, os dois bits de código acabam sendo os mesmos.

Wilka
fonte
Esse é um ponto interessante. Eu sempre pensei que os objetos não saem do escopo até que o método em que eles estão no escopo esteja completo. A menos que o objeto tenha um escopo definido dentro de um bloco Using ou seja explicitamente definido como Nothing ou null.
Guru Josh
1
A maneira preferida para garantir que eles permanecer vivo é usar GC.KeepAlive(someType); See ericlippert.com/2013/06/10/construction-destruction
NotMe
7

Além disso:

using(SomeObject object = new SomeObject()) 
{
  // do stuff with the object
}
// the object will be disposed of
Steve Tranby
fonte
7

Em geral, não é necessário anular objetos após o uso, mas, em alguns casos, acho que é uma boa prática.

Se um objeto implementa IDisposable e é armazenado em um campo, acho bom anulá-lo, apenas para evitar o uso do objeto descartado. Os erros do seguinte tipo podem ser dolorosos:

this.myField.Dispose();
// ... at some later time
this.myField.DoSomething();

É bom anular o campo após descartá-lo e obter um NullPtrEx diretamente na linha em que o campo é usado novamente. Caso contrário, você poderá encontrar algum bug enigmático na linha (dependendo exatamente do que o DoSomething faz).

dbkk
fonte
8
Bem, um objeto descartado deve lançar ObjectDisposedException se já tiver sido descartado. Isso, até onde eu sei, exige código padrão em todos os lugares, mas, novamente, Disposed é um paradigma mal pensado de qualquer maneira.
precisa saber é o seguinte
3
Ctrl + F para .Dispose(). Se você o encontrar, não estará usando o IDisposable corretamente. O único uso para um objeto descartável deve estar nos limites de um bloco de uso. E após o bloco de uso, você nem tem mais acesso myField. E dentro do bloco using, a configuração para nullnão é necessária, o bloco using descartará o objeto para você.
Suamere 11/01
7

As chances são de que seu código não seja estruturado de maneira suficientemente rígida se você sentir a necessidade de nullvariáveis.

Existem várias maneiras de limitar o escopo de uma variável:

Como mencionado por Steve Tranby

using(SomeObject object = new SomeObject()) 
{
  // do stuff with the object
}
// the object will be disposed of

Da mesma forma, você pode simplesmente usar colchetes:

{
    // Declare the variable and use it
    SomeObject object = new SomeObject()
}
// The variable is no longer available

Acho que usar colchetes sem nenhum "cabeçalho" para realmente limpar o código e ajudar a torná-lo mais compreensível.

mbillard
fonte
Tentei usar escopos locais personalizados uma vez (principalmente sendo um smarta $$). A empresa explodiu.
Suamere 11/01
Em outra nota: Isso ocorre porque o compilador c # encontrará variáveis ​​de escopo local que implementam IDisposable e chamará .Dispose (a maior parte do tempo) quando seu escopo terminar. No entanto ... As conexões SQL são um grande momento em que .Dispose () nunca é otimizado. Existem alguns tipos que exigem atenção explícita, então eu sempre faço as coisas explicitamente, só para não ser mordido.
Suamere
5

O único momento em que você deve definir uma variável como nula é quando a variável não sai do escopo e você não precisa mais dos dados associados a ela. Caso contrário, não há necessidade.

Prumo
fonte
2
Isso é verdade, mas também significa que você provavelmente deve refatorar seu código. Acho que nunca precisei declarar uma variável fora do escopo pretendido.
Karl Seguin
2
Se "variável" é entendido como incluindo campos de objetos, essa resposta faz muito sentido. No caso em que "variável" significa apenas "variável local" (de um método), provavelmente estamos falando de casos de nicho aqui (por exemplo, um método que é executado por um período de tempo muito maior que o normal).
stakx - não está mais contribuindo
5

Em geral, não é necessário definir como nulo. Mas suponha que você tenha uma funcionalidade Redefinir em sua classe.

Você pode fazer isso, porque não deseja chamar o descarte duas vezes, pois parte do Dispose pode não ser implementada corretamente e lança a exceção System.ObjectDisposed.

private void Reset()
{
    if(_dataset != null)
    {
       _dataset.Dispose();
       _dataset = null;
    }
    //..More such member variables like oracle connection etc. _oraConnection
 }
Munish Goyal
fonte
Talvez seja melhor rastrear isso com uma bandeira separada.
Thulani Chivandikwa 13/09/19
3

esse tipo de "não há necessidade de definir objetos como nulos após o uso" não é totalmente preciso. Há momentos em que você precisa NULL a variável após descartá-la.

Sim, você sempre deve ligar .Dispose()ou usar .Close()qualquer coisa que tenha quando terminar. Seja identificadores de arquivo, conexões de banco de dados ou objetos descartáveis.

Separado disso, está o padrão muito prático do LazyLoad.

Digamos que eu tenha e instanciado ObjAde class A. Class Atem uma propriedade pública chamada PropBde class B.

Internamente, PropBusa a variável privada de _Be o padrão é null. Quando PropB.Get()é usado, ele verifica para ver se _PropBé nulo e se for, abre os recursos necessários para instanciar um Bem _PropB. Em seguida, retorna _PropB.

Para minha experiência, este é um truque realmente útil.

Onde a necessidade de nulo aparece é se você redefinir ou alterar A de alguma forma que o conteúdo _PropBfosse filho dos valores anteriores A, será necessário Dispose AND null out _PropBpara que LazyLoad possa redefinir para buscar o valor correto SE o código requer isso.

Se você fizer isso _PropB.Dispose()e logo depois esperar que a verificação nula do LazyLoad seja bem-sucedida, ela não será nula e você verá dados obsoletos. Na verdade, você deve anulá-lo depois Dispose()apenas para ter certeza.

Eu certamente gostaria que fosse de outro modo, mas agora tenho código que exibe esse comportamento depois de uma função Dispose()a _PropBe fora da função de chamada que fez o Dispose (e, portanto, quase fora do escopo), o objeto privado ainda não é nulo, e os dados antigos ainda estão lá.

Eventualmente, a propriedade descartada será anulada, mas isso não foi determinista da minha perspectiva.

O principal motivo, como o dbkk faz alusão, é que o contêiner pai ( ObjAcom PropB) mantém a instância _PropBno escopo, apesar do Dispose().

KenF
fonte
Um bom exemplo de como configurar nulo manualmente significa um erro mais fatal para o chamador, o que é uma coisa boa.
rola
1

Existem alguns casos em que faz sentido anular referências. Por exemplo, ao escrever uma coleção - como uma fila de prioridade - e pelo seu contrato, você não deve manter esses objetos ativos para o cliente depois que o cliente os remove da fila.

Mas esse tipo de coisa só importa em coleções de vida longa. Se a fila não sobreviver ao final da função em que foi criada, isso importa muito menos.

No geral, você realmente não deveria se preocupar. Deixe o compilador e o GC fazerem o trabalho deles para que você possa fazer o seu.

Patrick
fonte
1

Dê uma olhada neste artigo também: http://www.codeproject.com/KB/cs/idisposable.aspx

Na maioria das vezes, definir um objeto como nulo não tem efeito. O único momento em que você deve fazer isso é se estiver trabalhando com um "objeto grande", que é maior que 84K de tamanho (como bitmaps).

Scott Dorman
fonte
1

Stephen Cleary explica muito bem neste post: Devo definir variáveis ​​como nulo para ajudar na coleta de lixo?

Diz:

A resposta curta, para o impaciente Sim, se a variável for um campo estático ou se você estiver escrevendo um método enumerável (usando retorno de rendimento) ou um método assíncrono (usando assíncrono e aguardar). Caso contrário, não.

Isso significa que, em métodos regulares (não enumeráveis ​​e não assíncronos), você não define variáveis ​​locais, parâmetros de método ou campos de instância como nulos.

(Mesmo se você estiver implementando IDisposable.Dispose, ainda não deve definir variáveis ​​como nulas).

O importante que devemos considerar são os campos estáticos .

Os campos estáticos são sempre objetos raiz , portanto, sempre são considerados "vivos" pelo coletor de lixo. Se um campo estático referenciar um objeto que não é mais necessário, ele deverá ser definido como nulo, para que o coletor de lixo o trate como elegível para coleta.

Definir campos estáticos como nulos não faz sentido se todo o processo estiver sendo encerrado. Todo o heap está prestes a ser coletado como lixo nesse ponto, incluindo todos os objetos raiz.

Conclusão:

Campos estáticos ; é sobre isso. Qualquer outra coisa é uma perda de tempo .

Ebleme
fonte
0

Acredito que, pelo design dos implementadores de GC, você não pode acelerar o GC com a anulação. Tenho certeza de que eles preferem que você não se preocupe com como / quando o GC é executado - trate-o como este Onipresente Sendo protegendo e cuidando de você ... (inclina a cabeça, levanta o punho para o céu) .. .

Pessoalmente, geralmente defino explicitamente variáveis ​​como nulas quando termino com elas como uma forma de auto-documentação. Não declaro, uso e defino como nulo mais tarde - anulo imediatamente depois que eles não são mais necessários. Estou dizendo, explicitamente: "Eu terminei oficialmente com você ... se foi ..."

A anulação é necessária em um idioma do GC? Não. É útil para o GC? Talvez sim, talvez não, não sei ao certo, por design, eu realmente não posso controlá-lo e, independentemente da resposta de hoje com esta versão ou aquela, futuras implementações de GC podem mudar a resposta além do meu controle. Além disso, se / quando a anulação for otimizada, será pouco mais que um comentário sofisticado, se você desejar.

Eu acho que se isso esclarece minha intenção para o próximo pobre idiota que segue meus passos, e se "pode" potencialmente ajudar a GC algumas vezes, então vale a pena para mim. Principalmente, isso me faz sentir arrumado e claro, e Mongo gosta de me sentir arrumado e claro. :)

Eu vejo assim: Linguagens de programação existem para permitir que outras pessoas tenham uma idéia da intenção e um compilador faça uma solicitação de trabalho sobre o que fazer - o compilador converte essa solicitação em um idioma diferente (às vezes várias) para uma CPU - a (s) CPU (s) podem dizer o que você usou, as configurações de sua guia, comentários, ênfases estilísticas, nomes de variáveis, etc. Muitas coisas escritas no código não se convertem no que é consumido pela CPU na sequência especificada. Nosso C, C ++, C #, Lisp, Babel, assembler ou o que quer que seja teoria e não realidade, escrito como uma declaração de trabalho. O que você vê não é o que você recebe, sim, mesmo na linguagem assembler.

Entendo que a mentalidade de "coisas desnecessárias" (como linhas em branco) "nada mais é do que barulho e confusão de código". Essa fui eu no início da minha carreira; Eu entendo isso totalmente. Neste momento, eu me inclino para o que torna o código mais claro. Não é como se eu estivesse adicionando até 50 linhas de "ruído" aos meus programas - são algumas linhas aqui ou ali.

Há exceções para qualquer regra. Em cenários com memória volátil, memória estática, condições de corrida, singletons, uso de dados "obsoletos" e todo esse tipo de apodrecimento, isso é diferente: você PRECISA gerenciar sua própria memória, bloqueando e anulando conforme apropriado, porque a memória não faz parte da memória. o universo da GC - espero que todos entendam isso. O resto do tempo com os idiomas do GC é uma questão de estilo, e não de necessidade ou um aumento garantido de desempenho.

No final do dia, entenda o que é elegível para o GC e o que não é; bloquear, descartar e anular adequadamente; cera, cera; inspire, expire; e por tudo o mais eu digo: se é bom, faça. Sua milhagem pode variar ... como deveria ...

John
fonte
0

Eu acho que definir algo de volta para null é confuso. Imagine um cenário em que o item que está sendo definido agora seja exposto, digamos, através da propriedade Agora, de alguma forma, algum pedaço de código acidentalmente usa essa propriedade depois que o item é descartado, você receberá uma exceção de referência nula que requer alguma investigação para descobrir exatamente o que está acontecendo.

Acredito que os descartáveis ​​de estrutura permitirão lançar ObjectDisposedException que é mais significativo. Não configurá-los de volta para nulo seria melhor do que por esse motivo.

Thulani Chivandikwa
fonte
-1

Algum objeto supõe o .dispose()método que força o recurso a ser removido da memória.

GateKiller
fonte
11
Não, não faz; Dispose () não coleta o objeto - ele é usado para executar uma limpeza determinística, geralmente liberando recursos não gerenciados.
Marc Gravell
1
Tendo em conta que o determinismo se aplica apenas aos recursos gerenciados, e não os não gerenciados (isto é memória)
nicodemus13