Pergunta

A rede Padrão IDisposable implica que se você escrever um finalizador e implementar IDisposable, seu finalizador precisará chamar explicitamente Dispose.Isso é lógico e é o que sempre fiz nas raras situações em que um finalizador é necessário.

No entanto, o que acontece se eu fizer isso:

class Foo : IDisposable
{
     public void Dispose(){ CloseSomeHandle(); }
}

e não implemente um finalizador, nem nada.A estrutura chamará o método Dispose para mim?

Sim, eu sei que isso parece idiota, e toda a lógica implica que não, mas sempre tive duas coisas na minha cabeça que me deixaram inseguro.

  1. Alguém, há alguns anos, me disse uma vez que isso de fato aconteceria, e essa pessoa tinha um histórico muito sólido de "conhecer o que estava fazendo".

  2. O compilador/estrutura faz outras coisas 'mágicas' dependendo de quais interfaces você implementa (por exemplo:foreach, métodos de extensão, serialização baseada em atributos, etc.), então faz sentido que isso também possa ser 'mágico'.

Embora eu tenha lido muitas coisas sobre isso e haja muitas coisas implícitas, nunca consegui encontrar um definitivo Sim ou Não, responda a esta pergunta.

Foi útil?

Solução

O .Net Garbage Collector chama o método Object.Finalize de um objeto na coleta de lixo.Por padrão isso faz nada e deve ser substituído se você quiser liberar recursos adicionais.

Dispose NÃO é chamado automaticamente e deve ser explicitamente chamado se os recursos devem ser liberados, como dentro de um bloco 'usando' ou 'tentar finalmente'

ver http://msdn.microsoft.com/en-us/library/system.object.finalize.aspx Para maiores informações

Outras dicas

Quero enfatizar o ponto de vista de Brian em seu comentário, porque é importante.

Finalizadores não são destruidores determinísticos como em C++.Como outros apontaram, não há garantia de quando será chamado e, de fato, se você tiver memória suficiente, se será sempre ser chamado.

Mas o lado ruim dos finalizadores é que, como disse Brian, eles fazem com que seu objeto sobreviva a uma coleta de lixo.Isso pode ser ruim.Por que?

Como você pode ou não saber, o GC é dividido em gerações - Gen 0, 1 e 2, além do Large Object Heap.Divisão é um termo vago - você obtém um bloco de memória, mas há indicações de onde os objetos da Geração 0 começam e terminam.

O processo de pensamento é que você provavelmente usará muitos objetos que terão vida curta.Portanto, esses devem ser fáceis e rápidos para o GC chegar aos objetos da Geração 0.Portanto, quando há pressão de memória, a primeira coisa que faz é uma coleção da Geração 0.

Agora, se isso não resolver a pressão suficiente, ele volta e faz uma varredura Gen 1 (refazendo a Gen 0) e, se ainda não for suficiente, faz uma varredura Gen 2 (refazendo a Gen 1 e a Gen 0).Portanto, a limpeza de objetos de longa duração pode demorar um pouco e ser bastante cara (já que seus threads podem ficar suspensos durante a operação).

Isso significa que se você fizer algo assim:

~MyClass() { }

Seu objeto, não importa o que aconteça, viverá até a Geração 2.Isso ocorre porque o GC não tem como chamar o finalizador durante a coleta de lixo.Assim, os objetos que precisam ser finalizados são movidos para uma fila especial para serem limpos por um thread diferente (o thread finalizador - que, se você matar, fará com que todos os tipos de coisas ruins aconteçam).Isso significa que seus objetos permanecem por mais tempo e potencialmente forçam mais coletas de lixo.

Então, tudo isso é apenas para deixar claro que você deseja usar IDisposable para limpar recursos sempre que possível e tentar seriamente encontrar maneiras de contornar o uso do finalizador.É do interesse do seu aplicativo.

Já há muita discussão boa aqui e estou um pouco atrasado para a festa, mas gostaria de acrescentar alguns pontos.

  • O coletor de lixo nunca executará diretamente um método Dispose para você.
  • O CG vai execute finalizadores quando desejar.
  • Um padrão comum usado para objetos que possuem um finalizador é fazer com que ele chame um método que é definido por convenção como Dispose(bool disposing) passando false para indicar que a chamada foi feita devido à finalização, em vez de uma chamada Dispose explícita.
  • Isso ocorre porque não é seguro fazer suposições sobre outros objetos gerenciados ao finalizar um objeto (eles podem já ter sido finalizados).

class SomeObject : IDisposable {
 IntPtr _SomeNativeHandle;
 FileStream _SomeFileStream;

 // Something useful here

 ~ SomeObject() {
  Dispose(false);
 }

 public void Dispose() {
  Dispose(true);
 }

 protected virtual void Dispose(bool disposing) {
  if(disposing) {
   GC.SuppressFinalize(this);
   //Because the object was explicitly disposed, there will be no need to 
   //run the finalizer.  Suppressing it reduces pressure on the GC

   //The managed reference to an IDisposable is disposed only if the 
   _SomeFileStream.Dispose();
  }

  //Regardless, clean up the native handle ourselves.  Because it is simple a member
  // of the current instance, the GC can't have done anything to it, 
  // and this is the onlyplace to safely clean up

  if(IntPtr.Zero != _SomeNativeHandle) {
   NativeMethods.CloseHandle(_SomeNativeHandle);
   _SomeNativeHandle = IntPtr.Zero;
  }
 }
}

Essa é a versão simples, mas há muitas nuances que podem atrapalhar esse padrão.

  • O contrato para IDisposable.Dispose indica que deve ser seguro chamar várias vezes (chamar Dispose em um objeto que já foi descartado não deve fazer nada)
  • Pode ser muito complicado gerenciar adequadamente uma hierarquia de herança de objetos descartáveis, especialmente se diferentes camadas introduzirem novos recursos descartáveis ​​e não gerenciados.No padrão acima, Dispose(bool) é virtual para permitir que seja substituído para que possa ser gerenciado, mas acho que é propenso a erros.

Na minha opinião, é muito melhor evitar completamente qualquer tipo que contenha diretamente referências descartáveis ​​​​e recursos nativos que possam exigir finalização.SafeHandles fornece uma maneira muito limpa de fazer isso, encapsulando recursos nativos em descartáveis ​​que fornecem internamente sua própria finalização (juntamente com uma série de outros benefícios, como a remoção da janela durante P/Invoke, onde um identificador nativo pode ser perdido devido a uma exceção assíncrona) .

A simples definição de um SafeHandle torna isso trivial:


private class SomeSafeHandle
 : SafeHandleZeroOrMinusOneIsInvalid {
 public SomeSafeHandle()
  : base(true)
  { }

 protected override bool ReleaseHandle()
 { return NativeMethods.CloseHandle(handle); }
}

Permite simplificar o tipo que contém para:


class SomeObject : IDisposable {
 SomeSafeHandle _SomeSafeHandle;
 FileStream _SomeFileStream;
 // Something useful here
 public virtual void Dispose() {
  _SomeSafeHandle.Dispose();
  _SomeFileStream.Dispose();
 }
}

Eu não acho.Você tem controle sobre quando Dispose é chamado, o que significa que você poderia, em teoria, escrever um código de descarte que faça suposições sobre (por exemplo) a existência de outros objetos.Você não tem controle sobre quando o finalizador é chamado, portanto, seria duvidoso fazer com que o finalizador chamasse Dispose automaticamente em seu nome.


EDITAR:Fui embora e testei, só para ter certeza:

class Program
{
    static void Main(string[] args)
    {
        Fred f = new Fred();
        f = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine("Fred's gone, and he's not coming back...");
        Console.ReadLine();
    }
}

class Fred : IDisposable
{
    ~Fred()
    {
        Console.WriteLine("Being finalized");
    }

    void IDisposable.Dispose()
    {
        Console.WriteLine("Being Disposed");
    }
}

Não no caso que você descreve, mas o GC chamará o Finalizador para você, se você tiver um.

NO ENTANTO.Na próxima coleta de lixo, ao invés de ser coletado, o objeto irá para a finalização que, tudo é coletado, então é chamado o finalizador.A próxima coleção depois disso será liberada.

Dependendo da pressão de memória do seu aplicativo, você pode não ter um gc para a geração desse objeto por um tempo.Portanto, no caso de, digamos, um fluxo de arquivo ou uma conexão de banco de dados, pode ser necessário esperar um pouco para que o recurso não gerenciado seja liberado na chamada do finalizador, causando alguns problemas.

Não, não é chamado.

Mas isso facilita não esquecer de descartar seus objetos.Basta usar o using palavra-chave.

Fiz o seguinte teste para isso:

class Program
{
    static void Main(string[] args)
    {
        Foo foo = new Foo();
        foo = null;
        Console.WriteLine("foo is null");
        GC.Collect();
        Console.WriteLine("GC Called");
        Console.ReadLine();
    }
}

class Foo : IDisposable
{
    public void Dispose()
    {

        Console.WriteLine("Disposed!");
    }

O GC irá não ligue para descartar.Isto poderia ligue para o seu finalizador, mas mesmo isso não é garantido em todas as circunstâncias.

Veja isso artigo para uma discussão sobre a melhor maneira de lidar com isso.

A documentação sobre IDisponível fornece uma explicação bastante clara e detalhada do comportamento, bem como um exemplo de código.O GC NÃO chamará o Dispose() método na interface, mas chamará o finalizador do seu objeto.

O padrão IDisposable foi criado principalmente para ser chamado pelo desenvolvedor, se você possui um objeto que implementa IDispose o desenvolvedor deve implementar o using palavra-chave em torno do contexto do objeto ou chame o método Dispose diretamente.

A segurança contra falhas do padrão é implementar o finalizador chamando o método Dispose().Se você não fizer isso, poderá criar alguns vazamentos de memória, ou seja:Se você criar algum wrapper COM e nunca chamar o System.Runtime.Interop.Marshall.ReleaseComObject(comObject) (que seria colocado no método Dispose).

Não há mágica no clr para chamar métodos Dispose automaticamente, a não ser rastrear objetos que contêm finalizadores e armazená-los na tabela Finalizer pelo GC e chamá-los quando alguma heurística de limpeza entrar em ação pelo GC.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top