Você precisa descartar objects e defini-los como nulos?

Você precisa descartar objects e defini-los como nulos, ou o coletor de lixo os limpará quando eles saírem do escopo?

Os objects serão limpos quando não estiverem mais sendo usados ​​e quando o coletor de lixo achar adequado. Às vezes, você pode precisar definir um object como null para torná-lo fora do escopo (como um campo estático cujo valor você não precisa mais), mas geralmente não há necessidade de definir como null .

Quanto a objects descartáveis, eu concordo com @Andre. Se o object for IDisposable , é uma boa ideia descartá-lo quando você não precisar mais dele, especialmente se o object usar resources não gerenciados. Não descartar resources não gerenciados levará a vazamentos de memory .

Você pode usar a instrução using para descartar automaticamente um object quando o programa deixar o escopo da instrução using .

 using (MyIDisposableObject obj = new MyIDisposableObject()) { // use the object here } // the object is disposed here 

Qual é funcionalmente equivalente a:

 MyIDisposableObject obj; try { obj = new MyIDisposableObject(); } finally { if (obj != null) { ((IDisposable)obj).Dispose(); } } 

Objetos nunca saem do escopo em C # como em C ++. Eles são tratados pelo Garbage Collector automaticamente quando eles não são mais usados. Essa é uma abordagem mais complicada do que o C ++, em que o escopo de uma variável é inteiramente determinístico. O coletor de lixo CLR percorre ativamente todos os objects que foram criados e funcionam se estiverem sendo usados.

Um object pode ficar “fora do escopo” em uma function, mas se seu valor for retornado, o GC verificará se a function de chamada mantém ou não o valor de retorno.

A definição de referências de objects para null é desnecessária, pois a garbage collection funciona ao determinar quais objects estão sendo referenciados por outros objects.

Na prática, você não precisa se preocupar com a destruição, simplesmente funciona e é ótimo 🙂

Dispose deve ser chamado em todos os objects que implementam IDisposable quando você terminar de trabalhar com eles. Normalmente você usaria um bloco de using com esses objects da seguinte forma:

 using (var ms = new MemoryStream()) { //... } 

EDIT No escopo da variável. Craig perguntou se o escopo da variável tem algum efeito na vida útil do object. Para explicar corretamente esse aspecto do CLR, precisarei explicar alguns conceitos de C ++ e C #.

Escopo variável real

Em ambas as linguagens, a variável só pode ser usada no mesmo escopo que foi definida – class, function ou um bloco de instruções delimitado por chaves. A diferença sutil, no entanto, é que, em C #, as variables ​​não podem ser redefinidas em um bloco nested.

Em C ++, isso é perfeitamente legal:

 int iVal = 8; //iVal == 8 if (iVal == 8){ int iVal = 5; //iVal == 5 } //iVal == 8 

Em C #, no entanto, você recebe um erro do compilador:

 int iVal = 8; if(iVal == 8) { int iVal = 5; //error CS0136: A local variable named 'iVal' cannot be declared in this scope because it would give a different meaning to 'iVal', which is already used in a 'parent or current' scope to denote something else } 

Isso faz sentido se você olhar para o MSIL gerado – todas as variables ​​usadas pela function são definidas no início da function. Dê uma olhada nesta function:

 public static void Scope() { int iVal = 8; if(iVal == 8) { int iVal2 = 5; } } 

Abaixo está o IL gerado. Note que iVal2, que é definido dentro do bloco if, é realmente definido no nível da function. Efetivamente, isso significa que o C # só tem escopo de nível de class e function no que diz respeito ao tempo de vida variável.

 .method public hidebysig static void Scope() cil managed { // Code size 19 (0x13) .maxstack 2 .locals init ([0] int32 iVal, [1] int32 iVal2, [2] bool CS$4$0000) //Function IL - omitted } // end of method Test2::Scope 

Escopo e vida útil do object C ++

Sempre que uma variável C ++, alocada na pilha, sai do escopo, ela é destruída. Lembre-se que em C ++ você pode criar objects na pilha ou no heap. Quando você os cria na pilha, assim que a execução sai do escopo, eles saem da pilha e são destruídos.

 if (true) { MyClass stackObj; //created on the stack MyClass heapObj = new MyClass(); //created on the heap obj.doSomething(); } //<-- stackObj is destroyed //heapObj still lives 

Quando objects C ++ são criados no heap, eles devem ser explicitamente destruídos, caso contrário, é um memory leaks. Nenhum tal problema com variables ​​de pilha embora.

Vida útil do object C #

No CLR, objects (ou seja, tipos de referência) são sempre criados no heap gerenciado. Isso é reforçado pela syntax de criação de objects. Considere este trecho de código.

 MyClass stackObj; 

Em C ++, isso criaria uma instância no MyClass na pilha e chamaria seu construtor padrão. Em C #, criaria uma referência à class MyClass que não aponta para nada. A única maneira de criar uma instância de uma class é usando o operador new :

 MyClass stackObj = new MyClass(); 

De certa forma, objects C # são muito parecidos com objects que são criados usando new syntax em C ++ - eles são criados no heap mas, ao contrário dos objects C ++, eles são gerenciados pelo tempo de execução, portanto você não precisa se preocupar em destruí-los.

Como os objects estão sempre no heap, o fato de as referências de objects (ou seja, os pointers) ficarem fora do escopo torna-se irrelevante. Há mais fatores envolvidos na determinação de se um object deve ser coletado do que simplesmente a presença de referências ao object.

Referências do object c #

Jon Skeet comparou referências de objects em Java a pedaços de string anexados ao balão, que é o object. Mesma analogia se aplica a referências de object C #. Eles simplesmente apontam para um local do heap que contém o object. Assim, defini-lo como nulo não tem efeito imediato no tempo de vida do object, o balão continua existindo, até que o GC o "cole".

Continuando com a analogia do balão, parece lógico que, uma vez que o balão não tenha cadeias de caracteres ligadas a ele, ele pode ser destruído. Na verdade, é exatamente assim que os objects de referência contados funcionam em idiomas não gerenciados. Exceto que esta abordagem não funciona muito bem para referências circulares. Imagine dois balões que são unidos por uma corda, mas nenhum balão tem uma string para qualquer outra coisa. Em regras simples de contagem de referências, as duas continuam a existir, 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 se abre (o GC funciona) - os balões não utilizados flutuam para longe, embora possa haver grupos de balões que estão amarrados 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 objects que foram alocados mais recentemente, já que é mais provável que eles não sejam usados ​​e marcar e varrer envolve o tempo de execução percorrendo todo o gráfico de objects e trabalhando se houver grupos de objects não usados. Isso lida adequadamente com o problema de dependência circular.

Além disso, o .NET GC é executado em outro encadeamento (chamado de encadeamento finalizador), já que ele tem um pouco a fazer e fazer isso no encadeamento principal interromperia seu programa.

Como outros disseram, você definitivamente deseja chamar Dispose se a class implementar IDisposable . Eu tomo uma posição bastante rígida sobre isso. Alguns podem alegar que chamar Dispose on DataSet , por exemplo, é inútil porque eles desmontaram e viram que não fizeram nada significativo. Mas eu acho que há falácias abundantes nesse argumento.

Leia isto para um interessante debate por indivíduos respeitados sobre o assunto. Então leia meu raciocínio aqui porque eu acho que Jeffery Richter está no campo errado.

Agora, para saber se você deve ou não definir uma referência para null . A resposta é não. Deixe-me ilustrar meu ponto com o seguinte código.

 public static void Main() { Object a = new Object(); Console.WriteLine("object created"); DoSomething(a); Console.WriteLine("object used"); a = null; Console.WriteLine("reference set to null"); } 

Então, quando você acha que o object referenciado por a é elegível para coleta? Se você disse depois da chamada para a = null então você está errado. Se você disse depois que o método Main é concluído, você também está errado. A resposta correta é que ele é elegível para coleta em algum momento durante a chamada para DoSomething . Isso está certo. É elegível antes de a referência ser definida como null e talvez até mesmo antes que a chamada para DoSomething concluída. Isso ocorre porque o compilador JIT pode reconhecer quando referências a objects não são mais desreferenciadas, mesmo se ainda estiverem com raiz.

Você nunca precisa definir objects para null em c #. O compilador e o tempo de execução cuidarão de descobrir quando não estão mais no escopo.

Sim, você deve descartar objects que implementam IDisposable.

Eu concordo com a resposta comum aqui que sim você deve dispor e não você geralmente não deve definir a variável como null … mas eu queria salientar que dispor não é principalmente sobre gerenciamento de memory. Sim, pode ajudar (e às vezes faz) com o gerenciamento de memory, mas seu objective principal é liberar deterministicamente os resources escassos.

Por exemplo, se você abrir uma porta de hardware (serial, por exemplo), um soquete TCP / IP, um arquivo (no modo de access exclusivo) ou mesmo uma conexão de database, impedirá que qualquer outro código use esses itens até serem liberados. Dispose geralmente libera esses itens (juntamente com GDI e outros “os” alças, etc, que existem milhares de disponíveis, mas ainda são limitados em geral). Se você não chamar dipose no object proprietário e liberar explicitamente esses resources, tente abrir o mesmo recurso novamente no futuro (ou outro programa faz) que a tentativa de abertura falhará porque seu object não coletado e não exposto ainda tem o item aberto . É claro, quando o GC coleta o item (se o padrão Dispose foi implementado corretamente), o recurso será liberado … mas você não sabe quando isso será, então você não sabe quando é seguro abra esse recurso. Esta é a questão principal Dispose trabalha ao redor. É claro que liberar essas alças geralmente libera memory também, e nunca liberá-las pode nunca liberar essa memory … daí toda a conversa sobre vazamentos de memory ou atrasos na limpeza de memory.

Eu vi exemplos do mundo real disso causando problemas. Por exemplo, eu vi aplicações web ASP.Net que eventualmente não conseguem se conectar ao database (embora por curtos períodos de tempo, ou até que o processo do servidor web seja reiniciado) porque o pool de conexão do servidor sql está cheio ‘… ou seja , tantas conexões foram criadas e não liberadas explicitamente em um período de tempo tão curto que nenhuma nova conexão pode ser criada e muitas das conexões no pool, embora não estejam ativas, ainda são referenciadas por objects não descobertos e não coletados e, portanto, podem t ser reutilizado. O descarte correto das conexões do database, quando necessário, garante que esse problema não aconteça (pelo menos, a menos que você tenha access simultâneo muito alto).

Se o object implementa IDisposable , então sim, você deve descartá-lo. O object pode estar pendurado em resources nativos (identificadores de arquivos, objects do sistema operacional) que podem não ser liberados imediatamente caso contrário. Isso pode levar à privação de resources, problemas de bloqueio de arquivos e outros bugs sutis que poderiam ser evitados.

Consulte também Implementando um método Dispose no MSDN.

Se eles implementarem a interface IDisposable, você deverá descartá-los. O coletor de lixo cuidará do resto.

EDIT: melhor é usar o comando using ao trabalhar com itens descartáveis:

 using(var con = new SqlConnection("..")){ ... 

Normalmente, não há necessidade de definir campos como nulos. Eu sempre recomendo descartar resources não gerenciados no entanto.

Da experiência eu também aconselho você a fazer o seguinte:

  • Cancele a inscrição de events se você não precisar mais deles.
  • Defina qualquer campo que mantenha um delegado ou uma expressão como nulo se não for mais necessário.

Eu me deparei com alguns muito difíceis de encontrar problemas que foram o resultado direto de não seguir o conselho acima.

Um bom lugar para fazer isso é Dispose (), mas mais cedo geralmente é melhor.

Em geral, se existir uma referência a um object, o coletor de lixo (GC) pode demorar algumas gerações para descobrir que um object não está mais em uso. O tempo todo o object permanece na memory.

Isso pode não ser um problema até você descobrir que seu aplicativo está usando muito mais memory do que o esperado. Quando isso acontece, conecte um gerenciador de perfis de memory para ver quais objects não estão sendo limpos. A definição de campos que fazem referência a outros objects como nulos e desmarque collections sobre o descarte pode realmente ajudar o GC a descobrir quais objects ele pode remover da memory. O GC recuperará a memory usada com mais rapidez, tornando o seu aplicativo muito menos cheio de memory e mais rápido.

Sempre ligue para descartar. Não vale a pena o risco. Grandes aplicativos corporativos gerenciados devem ser tratados com respeito. Nenhuma suposição pode ser feita ou então voltará a te morder.

Não escute o leppie.

Muitos objects não implementam IDisposable, então você não precisa se preocupar com eles. Se eles realmente saírem do escopo, eles serão liberados automaticamente. Também nunca me deparei com a situação em que precisei definir algo como nulo.

Uma coisa que pode acontecer é que muitos objects podem ser mantidos abertos. Isso pode aumentar muito o uso de memory do seu aplicativo. Às vezes é difícil descobrir se isso é, na verdade, um memory leaks ou se seu aplicativo está apenas fazendo muitas coisas.

Ferramentas de perfil de memory podem ajudar com coisas assim, mas pode ser complicado.

Além disso, sempre cancele a inscrição de events que não são necessários. Também tenha cuidado com a vinculação e controles do WPF. Não é uma situação normal, mas me deparei com uma situação em que eu tinha um controle WPF que estava sendo vinculado a um object subjacente. O object subjacente era grande e ocupava uma grande quantidade de memory. O controle do WPF estava sendo substituído por uma nova instância, e o antigo ainda estava por algum motivo. Isso causou um grande memory leaks.

Em hindsite o código foi mal escrito, mas o ponto é que você quer ter certeza de que as coisas que não são usadas saiam do escopo. Esse demorou muito tempo para encontrar um perfilador de memory, já que é difícil saber quais coisas na memory são válidas e o que não deveria estar lá.

Quando um object implementa IDisposable você deve chamar Dispose (ou Close , em alguns casos, que chamará Dispose para você).

Você normalmente não precisa definir objects como null , porque o GC saberá que um object não será mais usado.

Há uma exceção quando defino objects como null . Quando recupero muitos objects (do database) em que preciso trabalhar e os armazeno em uma coleção (ou matriz). Quando o “trabalho” é feito, eu definir o object para null , porque o GC não sabe que eu terminei de trabalhar com ele.

Exemplo:

 using (var db = GetDatabase()) { // Retrieves array of keys var keys = db.GetRecords(mySelection); for(int i = 0; i < keys.Length; i++) { var record = db.GetRecord(keys[i]); record.DoWork(); keys[i] = null; // GC can dispose of key now // The record had gone out of scope automatically, // and does not need any special treatment } } // end using => db.Dispose is called 

Eu tenho que responder também. O JIT gera tabelas junto com o código de sua análise estática do uso de variables. Essas inputs de tabela são as “GC-Roots” no quadro de pilhas atual. À medida que o ponteiro de instrução avança, essas inputs da tabela se tornam inválidas e estão prontas para a garbage collection. Portanto: Se for uma variável com escopo definido, você não precisará defini-la como null – o GC coletará o object. Se for um membro ou uma variável estática, você deve configurá-lo como nulo

Há uma boa discussão sobre o assunto (junto com a história por trás do padrão Dispose) neste episódio do .NET Rocks!

http://www.dotnetrocks.com/default.aspx?showNum=10