Você precisa descartar objetos e configurá-los como nulos ou o coletor de lixo os limpará quando ficarem fora do escopo?
310
Você precisa descartar objetos e configurá-los como nulos ou o coletor de lixo os limpará quando ficarem fora do escopo?
Dispose()
método! Essa é uma variação sutil dessa questão, mas importante porque o objeto que está sendo descartado não pode saber se está "saindo do escopo" (chamarDispose()
não é garantia). Mais aqui: stackoverflow.com/questions/6757048/…Respostas:
Os objetos serão limpos quando não estiverem mais sendo usados e quando o coletor de lixo achar conveniente. Às vezes, pode ser necessário definir um objeto para
null
torná-lo fora do escopo (como um campo estático cujo valor você não precisa mais), mas no geral geralmente não há necessidade de definirnull
.Em relação à disposição de objetos, concordo com @Andre. Se o objeto for
IDisposable
, é uma boa idéia descartá-lo quando você não precisar mais dele, especialmente se o objeto usar recursos não gerenciados. Não descartar recursos não gerenciados levará a vazamentos de memória .Você pode usar a
using
instrução para descartar automaticamente um objeto assim que seu programa sair do escopo dausing
instrução.Qual é funcionalmente equivalente a:
fonte
if (obj != null) ((IDisposable)obj).Dispose();
IDisposable
. Deixar de Dispor um objeto geralmente não causa vazamento de memória em nenhuma classe bem projetada. Ao trabalhar com recursos não gerenciados em C #, você deve ter um finalizador que ainda liberará os recursos não gerenciados. Isso significa que, em vez de desalocar os recursos quando isso deve ser feito, será adiado para quando o coletor de lixo finalizar o objeto gerenciado. Ainda pode causar muitos outros problemas (como bloqueios não lançados). Você deve descartar umIDisposable
pensamento!Os objetos nunca ficam fora do escopo em C #, como em C ++. Eles são tratados pelo Garbage Collector automaticamente quando não são mais usados. Essa é uma abordagem mais complicada que o C ++, onde o escopo de uma variável é inteiramente determinístico. O coletor de lixo CLR passa ativamente por todos os objetos que foram criados e funciona se eles estiverem sendo usados.
Um objeto pode ficar "fora do escopo" em uma função, mas se seu valor for retornado, o GC verificará se a função de chamada mantém ou não o valor de retorno.
Definir referências de objetos
null
como desnecessário, pois a coleta de lixo funciona, trabalhando para quais objetos estão sendo referenciados por outros objetos.Na prática, você não precisa se preocupar com a destruição, apenas funciona e é ótimo :)
Dispose
deve ser chamado em todos os objetos implementadosIDisposable
quando você terminar de trabalhar com eles. Normalmente você usaria umusing
bloco com esses objetos da seguinte maneira:EDIT No escopo variável. Craig perguntou se o escopo da variável tem algum efeito no tempo de vida do objeto. Para explicar adequadamente esse aspecto do CLR, precisarei explicar alguns conceitos de C ++ e C #.
Escopo da variável real
Nos dois idiomas, a variável só pode ser usada no mesmo escopo que foi definido - classe, função ou um bloco de instrução entre chaves. A diferença sutil, no entanto, é que, em C #, as variáveis não podem ser redefinidas em um bloco aninhado.
Em C ++, isso é perfeitamente legal:
Em C #, no entanto, você recebe um erro do compilador:
Isso faz sentido se você observar o MSIL gerado - todas as variáveis usadas pela função são definidas no início da função. Dê uma olhada nesta função:
Abaixo está a IL gerada. Observe que o iVal2, que é definido dentro do bloco if, é realmente definido no nível da função. Efetivamente, isso significa que o C # só tem escopo de nível de classe e função no que diz respeito à vida útil variável.
Escopo C ++ e vida útil do objeto
Sempre que uma variável C ++, alocada na pilha, sai do escopo, ela é destruída. Lembre-se de que, em C ++, você pode criar objetos na pilha ou na pilha. Quando você os cria na pilha, uma vez que a execução sai do escopo, eles são retirados da pilha e destruídos.
Quando objetos C ++ são criados no heap, eles devem ser explicitamente destruídos, caso contrário, é um vazamento de memória. Porém, não existe esse problema com as variáveis da pilha.
Vida útil do objeto C #
No CLR, os objetos (ou seja, tipos de referência) são sempre criados no heap gerenciado. Isso é reforçado ainda mais pela sintaxe de criação de objeto. Considere esse trecho de código.
Em C ++, isso criaria uma instância na
MyClass
pilha e chamaria seu construtor padrão. Em C #, criaria uma referência à classeMyClass
que não aponta para nada. A única maneira de criar uma instância de uma classe é usando onew
operador:De certa forma, os objetos C # são muito parecidos com os objetos criados usando a
new
sintaxe em C ++ - eles são criados no heap, mas, diferentemente dos objetos C ++, eles são gerenciados pelo tempo de execução, portanto você não precisa se preocupar em destruí-los.Como os objetos estão sempre na pilha, o fato de as referências a objetos (ou seja, ponteiros) ficarem fora do escopo torna-se discutível. Existem mais fatores envolvidos para determinar se um objeto deve ser coletado do que simplesmente a presença de referências ao objeto.
Referências de objeto em C #
Jon Skeet comparou as referências de objeto em Java a pedaços de string anexados ao balão, que é o objeto. A mesma analogia se aplica às referências de objeto C #. Eles simplesmente apontam para um local da pilha que contém o objeto. Assim, defini-lo como nulo não tem efeito imediato no tempo de vida do objeto, o balão continua a existir, até que o GC o "abra".
Continuando pela analogia do balão, parece lógico que, uma vez que o balão não tenha cordas, ele poderá ser destruído. De fato, é exatamente assim que os objetos contados de referência funcionam em linguagens não gerenciadas. Exceto que essa abordagem não funciona muito bem para referências circulares. Imagine dois balões que são presos por uma corda, mas nenhum deles tem uma corda para mais nada. Sob regras simples de contagem, ambas continuam existindo, mesmo que todo o grupo de balões seja "órfão".
Objetos .NET são muito parecidos com balões de hélio sob o teto. Quando o teto é aberto (o GC é executado) - os balões não utilizados flutuam para longe, mesmo que haja grupos de balões presos juntos.
O .NET GC usa uma combinação de GC geracional e marca e varredura. A abordagem geracional envolve o tempo de execução que favorece a inspeção de objetos alocados mais recentemente, pois é mais provável que eles não sejam utilizados e a marcação e a varredura envolvem o tempo de execução percorrendo todo o gráfico de objetos e resolvendo se há grupos de objetos que não são utilizados. Isso lida adequadamente com o problema de dependência circular.
Além disso, o .NET GC é executado em outro thread (o chamado thread do finalizador), pois tem muito o que fazer, e isso no thread principal interromperia o programa.
fonte
Como outros já disseram, você definitivamente deseja ligar
Dispose
se a classe implementarIDisposable
. Eu tomo uma posição bastante rígida sobre isso. Alguns afirmam poder que chamarDispose
emDataSet
, por exemplo, é inútil porque eles desmontaram e viu que ele não fez nada de significativo. Mas acho que existem muitas falácias nesse argumento.Leia isso para um debate interessante de pessoas respeitadas sobre o assunto. Então leia meu raciocínio aqui porque acho que Jeffery Richter está no campo errado.
Agora, se você deve ou não definir uma referência
null
. A resposta é não. Deixe-me ilustrar meu argumento com o seguinte código.Então, quando você acha que o objeto referenciado por
a
é elegível para coleta? Se você disse que após a ligaçãoa = null
, está errado. Se você disse que após a conclusão doMain
método, também está errado. A resposta correta é que ele é elegível para coleta em algum momento durante a chamada paraDoSomething
. Isso está certo. É elegível antes que a referência seja configuradanull
e talvez até antes da conclusão da chamadaDoSomething
. Isso ocorre porque o compilador JIT pode reconhecer quando as referências a objetos não são mais desreferenciadas, mesmo que ainda estejam enraizadas.fonte
a
houver um campo de membro privado em uma classe? Sea
não estiver definido como nulo, o GC não tem como saber sea
será usado novamente em algum método, certo? Portantoa
, não será coletado até que toda a classe que contém seja coletada. Não?a
fosse um membro da classe e a classe quea
ainda estivesse enraizada e em uso, ela também permaneceria por aí. Esse é um cenário em que configurá-lonull
pode ser benéfico.Dispose
é importante - não é possível invocarDispose
(ou qualquer outro método não-inlinável) em um objeto sem uma referência a ele; chamarDispose
depois que uma é feita usando um objeto garantirá que uma referência com raiz continuará a existir durante toda a última ação executada nele. O abandono de todas as referências a um objeto sem chamarDispose
pode ironicamente fazer com que os recursos do objeto sejam ocasionalmente liberados muito cedo .Você nunca precisa definir objetos como nulos em C #. O compilador e o tempo de execução cuidarão de descobrir quando eles não estão mais no escopo.
Sim, você deve descartar objetos que implementem IDisposable.
fonte
want
anule-a assim que terminar com ela, para que ela possa ser recuperada gratuitamente.Concordo com a resposta comum aqui de que sim, você deve descartar e não, geralmente não deve definir a variável como nula ... mas eu gostaria de ressaltar que descartar NÃO é principalmente sobre gerenciamento de memória. Sim, ele pode ajudar (e às vezes ajuda) no gerenciamento de memória, mas seu objetivo principal é fornecer a liberação determinística de recursos escassos.
Por exemplo, se você abrir uma porta de hardware (serial, por exemplo), um soquete TCP / IP, um arquivo (no modo de acesso exclusivo) ou mesmo uma conexão com o banco de dados, você impediu qualquer outro código de usar esses itens até que eles sejam liberados. Descarte geralmente libera esses itens (junto com GDI e outros identificadores de "sistema operacional" etc.), dos quais existem milhares de disponíveis, mas ainda são limitados no geral). Se você não chamar dipose no objeto proprietário e liberar explicitamente esses recursos, tente abrir o mesmo recurso novamente no futuro (ou outro programa o faça); a tentativa de abertura falhará porque seu objeto não exposto e não coletado ainda tem o item aberto . Obviamente, quando o GC coletar o item (se o padrão Dispose tiver sido implementado corretamente), o recurso será liberado ... mas você não sabe quando será, então você não não sei quando é seguro reabrir esse recurso. Esse é o principal problema do Dispose. Obviamente, liberar esses identificadores também libera memória também, e nunca liberá-los pode nunca liberar essa memória ... portanto, toda a conversa sobre vazamentos de memória ou atrasos na limpeza da memória.
Eu vi exemplos do mundo real disso causando problemas. Por exemplo, eu vi aplicativos Web do ASP.Net que eventualmente não conseguem se conectar ao banco de dados (embora por curtos períodos de tempo ou até que o processo do servidor da Web seja reiniciado) porque o pool de conexões do servidor sql está cheio ... , tantas conexões foram criadas e não liberadas explicitamente em tão pouco tempo que nenhuma nova conexão pode ser criada e muitas das conexões no pool, embora não ativas, ainda são referenciadas por objetos não digitados e não coletados e, portanto, podem ' Não seja reutilizado. O descarte correto das conexões com o banco de dados, sempre que necessário, garante que esse problema não ocorra (pelo menos não, a menos que você tenha acesso simultâneo muito alto).
fonte
Se o objeto for implementado
IDisposable
, sim, você deve descartá-lo. O objeto pode estar preso a recursos nativos (identificadores de arquivo, objetos do SO) que podem não ser liberados imediatamente caso contrário. Isso pode levar à falta de recursos, problemas de bloqueio de arquivos e outros erros sutis que poderiam ser evitados.Consulte também Implementando um método Dispose no MSDN.
fonte
Dispose
será chamado. Além disso, se o seu objeto estiver segurando um recurso escasso ou estiver bloqueando algum recurso (por exemplo, um arquivo), será necessário liberá-lo o mais rápido possível. Aguardar o GC fazer isso é subótimo.might
ligue, maswill
ligue.Se eles implementarem a interface IDisposable, você deverá descartá-los. O coletor de lixo cuidará do resto.
EDIT: melhor é usar o
using
comando ao trabalhar com itens descartáveis:fonte
Quando um objeto implementa
IDisposable
você deve chamarDispose
(ouClose
, em alguns casos, isso chamará Dispose para você).Você normalmente não precisa definir objetos
null
, porque o GC saberá que um objeto não será mais usado.Há uma exceção quando eu defino objetos como
null
. Quando recupero muitos objetos (do banco de dados) nos quais preciso trabalhar e os armazeno em uma coleção (ou matriz). Quando o "trabalho" é concluído, defino o objeto comonull
, porque o GC não sabe que terminei de trabalhar com ele.Exemplo:
fonte
Normalmente, não há necessidade de definir campos como nulos. Eu sempre recomendo a eliminação de recursos não gerenciados.
Por experiência, eu também aconselho você a fazer o seguinte:
Eu me deparei com alguns problemas muito difíceis de encontrar que foram o resultado direto de não seguir os conselhos acima.
Um bom lugar para fazer isso é em Dispose (), mas mais cedo é geralmente melhor.
Em geral, se existir uma referência a um objeto, o coletor de lixo (GC) poderá demorar mais algumas gerações para descobrir que um objeto não está mais em uso. O tempo todo o objeto permanece na memória.
Isso pode não ser um problema até você descobrir que seu aplicativo está usando muito mais memória do que você esperaria. Quando isso acontecer, conecte um perfilador de memória para ver quais objetos não estão sendo limpos. Definir campos que referenciam outros objetos como nulos e limpar coleções à disposição pode realmente ajudar o GC a descobrir quais objetos ele pode remover da memória. O GC recuperará a memória usada mais rapidamente, tornando seu aplicativo muito menos com fome e mais rápido.
fonte
Sempre chame o descarte. Não vale a pena o risco. Grandes aplicativos corporativos gerenciados devem ser tratados com respeito. Nenhuma suposição pode ser feita; caso contrário, ela voltará para morder você.
Não ouça leppie.
Muitos objetos não implementam o IDisposable, então você não precisa se preocupar com eles. Se realmente saírem do escopo, serão liberados automaticamente. Também nunca me deparei com a situação em que tive que definir algo como nulo.
Uma coisa que pode acontecer é que muitos objetos podem ser mantidos abertos. Isso pode aumentar bastante o uso de memória do seu aplicativo. Às vezes, é difícil descobrir se isso é realmente um vazamento de memória ou se seu aplicativo está apenas fazendo um monte de coisas.
As ferramentas de perfil de memória podem ajudar com coisas assim, mas pode ser complicado.
Além disso, sempre cancele a inscrição de eventos desnecessários. Também tenha cuidado com a ligação e os controles do WPF. Não é uma situação usual, mas me deparei com uma situação em que eu tinha um controle WPF que estava sendo vinculado a um objeto subjacente. O objeto subjacente era grande e consumia uma grande quantidade de memória. O controle WPF estava sendo substituído por uma nova instância, e a antiga ainda estava por aí por algum motivo. Isso causou um grande vazamento de memória.
No site posterior, o código foi mal escrito, mas o ponto é que você deseja garantir que as coisas que não são usadas fiquem fora do escopo. Aquele demorou muito tempo para encontrar com um criador de perfil de memória, pois é difícil saber quais itens na memória são válidos e o que não deveria estar lá.
fonte
Eu tenho que responder também. O JIT gera tabelas junto com o código a partir da análise estática do uso de variáveis. Essas entradas da tabela são as "Raízes do GC" no quadro de pilha atual. À medida que o ponteiro da instrução avança, essas entradas da tabela se tornam inválidas e prontas para a coleta de lixo. Portanto: se for uma variável com escopo definido, você não precisará defini-la como nula - o GC coletará o objeto. Se for um membro ou uma variável estática, você deverá configurá-lo como nulo
fonte