Quando se desfazer e por quê?

Eu fiz uma pergunta sobre esse método:

// Save an object out to the disk public static void SerializeObject(this T toSerialize, String filename) { XmlSerializer xmlSerializer = new XmlSerializer(toSerialize.GetType()); TextWriter textWriter = new StreamWriter(filename); xmlSerializer.Serialize(textWriter, toSerialize); textWriter.Close(); } 

na resposta eu recebi isso como uma observação adicional:

Certifique-se de sempre dispor de resources descartáveis, como streams e leitores de texto e escritores. Isso não parece ser o caso no seu método SerializeObject.

Então, eu posso dizer que isso vai parecer super coxo para alguém que tenha codificado C # por um ano ou dois, mas por que eu tenho que descartá-lo?

É ver que testWriter tem um método de descarte, mas a garbage collection não deve cuidar disso? Eu vim de Delphi para C #. Em Delphi eu tive que limpar tudo, então este não é um caso de eu querer ser preguiçosa. Acabei de saber que, se você forçar a liberar a memory que seus objects levam, isso pode causar coisas ruins. Foi-me dito para “Basta deixar o coletor de lixo fazê-lo”.

  1. Então, por que preciso ligar? (Meu palpite é que é porque o textWriter atinge o disco.)
  2. Existe uma lista de objects com os quais preciso ter cuidado? (Ou uma maneira fácil de saber quando preciso ligar?)

Você está certo de que, para um código escrito corretamente, o GC acabará limpando os resources nativos. O object terá um finalizador e, durante a finalização, liberará os resources nativos necessários.

No entanto, quando isso acontece é muito não determinista. Além disso, é um pouco para trás porque você está usando o GC, que foi projetado para lidar com memory gerenciada como um meio de gerenciar resources nativos. Isso leva a casos interessantes e pode fazer com que os resources nativos permaneçam vivos por muito mais tempo do que o esperado, levando a situações em que

  • Os arquivos estão abertos por muito tempo depois que eles não são mais usados
  • Identificadores de recurso podem esgotar-se porque o GC não vê pressão de memory suficiente para forçar uma coleção e, portanto, executar finalizadores

O padrão using / dispose adiciona determinismo à limpeza de resources nativos e remove esses problemas.

A regra aqui é bem simples: sempre chame Dispose() em objects que implementam IDisposable (nem todos os objects fazem). Você nem sempre saberá o motivo pelo qual um object teve que implementar o Dispose, mas você deve assumir que ele está lá por um motivo.

A maneira mais fácil de fazer isso é using :

 using (TextWriter tw = new StreamWriter(fileName)) { // your code here } 

Isto irá chamar Dispose() automaticamente no final do bloco de uso (é fundamentalmente o mesmo que usar um try / catch / finally com o Dispose () no bloco finally).

Para mais informações sobre como Dispose trabalha com garbage collection, veja aqui .

O coletor de lixo libera todos os resources, mas o tempo em que isso é feito é indefinido. O método Dispose fornece uma maneira de liberar resources não gerenciados imediatamente.

Se você sabe que não vai usar um certo recurso, pode simplesmente descartá-lo você mesmo; você certamente será mais rápido que o coletor de lixo e permitirá que outros usem o arquivo ou o que você abriu mais rápido. A maneira mais fácil seria usar o seu TextWriter ou qualquer outro recurso em um using :

 using (TextWriter textWriter = new StreamWriter(filename)) { xmlSerializer.Serialize(textWriter, toSerialize); } 

Isso basicamente garante que o TextWriter seja descartado no final. Você não precisa mais do que isso, de qualquer maneira.

Bem, na verdade você já está descartando isso, já que o método textWriter.Close faz isso.

 public virtual void Close() { this.Dispose(true); GC.SuppressFinalize(this); } 

Então você pode mudar seu código para. este

 public static void SerializeObject(this T toSerialize, String filename) { TextWriter textWriter; try { XmlSerializer xmlSerializer = new XmlSerializer(toSerialize.GetType()); textWriter = new StreamWriter(filename); xmlSerializer.Serialize(textWriter, toSerialize); } finally { textWriter.Close(); } 

O que é bem parecido com o que o using () faz nas outras respostas.

O impacto de não fazer isso é que, se ocorrer um erro com Serialize, levaria algum tempo até que o Framework desistisse de seu bloqueio de arquivo (quando Processar a fila fReachable).

Eu sei que o FxCop lhe diz quando implantar o IDisposable, mas eu não acho que haja uma maneira fácil de descobrir quando você precisa chamar Dispose além de olhar para o Docs e ver se um object implementa IDisposable (ou intellisense).

Se você estiver usando resources nativos (por exemplo, identificadores de arquivo), deverá chamar Dispose () para fechá-los em breve, e não quando o GC for executado (o que pode ser muito mais tarde em gerações gc mais altas). E você deseja fechar o arquivo, pois o access ao arquivo geralmente bloqueia o arquivo de alguma forma.

Se você estiver abrindo um recurso (como um arquivo ou abrindo uma conexão de database), a eliminação do recurso liberará o recurso. Se você não fizer isso, outras pessoas podem não conseguir se conectar ao database ou usar o arquivo.

Como regra geral … se a class implementa a interface IDisposable, você deve chamar o método Dispose () quando estiver terminando. Mais do que provável que houvesse uma razão para torná-lo descartável 🙂

Da documentação do TextWriter.Dispose :

Observação Sempre chame Dispose antes de liberar sua última referência ao TextWriter. Caso contrário, os resources que ele está usando não serão liberados até que o coletor de lixo chama o método Finalize do object TextWriter.

Na documentação do Object.Finalize :

A hora exata em que o finalizador é executado durante a garbage collection é indefinida. Não há garantias de que os resources sejam liberados em um horário específico, a menos que você chame um método Close ou Dispose.

e

O método Finalize pode não ser executado até a conclusão ou pode não ser executado nas seguintes circunstâncias excepcionais:

  • Outro finalizador bloqueia indefinidamente (entra em um loop infinito, tenta obter um bloqueio que nunca pode obter e assim por diante). Como o tempo de execução tenta executar os finalizadores até a conclusão, outros finalizadores podem não ser chamados se um finalizador bloquear indefinidamente.

  • O processo termina sem dar ao runtime a chance de limpar. Nesse caso, a primeira notificação do tempo de execução da finalização do processo é uma notificação DLL_PROCESS_DETACH.

O tempo de execução continua a Finalizar objects durante o desligamento somente enquanto o número de objects finalizáveis ​​continua a diminuir.

O coletor de lixo realmente “cuidará disso”. Cedo ou tarde. Quando chega a hora de chamar o finalizador, depois da próxima garbage collection.

Chamar Dispose garante que resources (como identificadores de arquivos) sejam liberados o mais rápido possível, tornando-os disponíveis para reutilização por outros processos no sistema. Na maioria das vezes, você não notará a diferença entre chamar Dispose yourself e deixar o coletor de lixo lidar com isso. Mas há momentos em que você vai. Um rastreador da Web, por exemplo, que cria muitos objects HttpWebResponse , ficará sem conexões rapidamente se você não descartá-los após o uso. (Não que eu tenha me deparado com esse problema … não, não eu.)

O motivo para chamar Dispose em vez de esperar pelo GC é geralmente porque você está usando um recurso de sistema finito que deseja liberar o mais rápido possível para que outros processos ou threads possam usá-lo. Para objects que possuem o padrão IDisposable, a construção “using” é uma maneira fácil e legível de se certificar de que Dispose é chamado.

Em geral, você deve descartar objects quando não precisar mais deles.

Não descartar objects quando você terminar com eles será um significado que irá bloquear o access a outros processados ​​/ aplicativos para eles.

Um programador que pensa que não precisa se preocupar em descartar coisas porque um finalizador vai cuidar delas é como um motorista que pensa que não precisa se preocupar em evitar colisões porque o carro tem um airbag. Sim, um airbag tornará as coisas mais resistentes, mas …