Uso do método Finalize / Dispose em C #

381

C # 2008

Estou trabalhando nisso há algum tempo e ainda estou confuso sobre o uso dos métodos finalize e descarte no código. Minhas perguntas estão abaixo:

  1. Eu sei que precisamos apenas de um finalizador enquanto dispomos de recursos não gerenciados. No entanto, se houver recursos gerenciados que fazem chamadas para recursos não gerenciados, ainda seria necessário implementar um finalizador?

  2. No entanto, se eu desenvolver uma classe que não use nenhum recurso não gerenciado - direta ou indiretamente, devo implementar o IDisposablepara permitir que os clientes dessa classe usem a 'declaração de uso'?

    Seria viável implementar IDisposable apenas para permitir que os clientes de sua classe usassem a instrução using?

    using(myClass objClass = new myClass())
    {
        // Do stuff here
    }
  3. Desenvolvi este código simples abaixo para demonstrar o uso Finalizar / descartar:

    public class NoGateway : IDisposable
    {
        private WebClient wc = null;
    
        public NoGateway()
        {
            wc = new WebClient();
            wc.DownloadStringCompleted += wc_DownloadStringCompleted;
        }
    
    
        // Start the Async call to find if NoGateway is true or false
        public void NoGatewayStatus()
        {
            // Start the Async's download
                // Do other work here
            wc.DownloadStringAsync(new Uri(www.xxxx.xxx));
        }
    
        private void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            // Do work here
        }
    
        // Dispose of the NoGateway object
        public void Dispose()
        {
            wc.DownloadStringCompleted -= wc_DownloadStringCompleted;
            wc.Dispose();
            GC.SuppressFinalize(this);
        }
    }

Pergunta sobre o código fonte:

  1. Aqui eu não adicionei o finalizador, e normalmente o finalizador será chamado pelo GC, e o finalizador chamará Dispose. Como não tenho o finalizador, quando chamo o método Dispose? É o cliente da classe que precisa chamá-lo?

    Portanto, minha classe no exemplo é chamada NoGateway e o cliente pode usar e descartar a classe assim:

    using(NoGateway objNoGateway = new NoGateway())
    {
        // Do stuff here   
    }

    O método Dispose seria chamado automaticamente quando a execução chegasse ao final do bloco using ou o cliente precisa chamar manualmente o método de descarte? ie

    NoGateway objNoGateway = new NoGateway();
    // Do stuff with object
    objNoGateway.Dispose(); // finished with it
  2. Eu estou usando a WebClientclasse na minha NoGatewayclasse. Como WebClientimplementa a IDisposableinterface, isso significa que WebClientindiretamente usa recursos não gerenciados? Existe uma regra rígida e rápida para seguir isso? Como sei que uma classe usa recursos não gerenciados?

ant2009
fonte
11
esse padrão de design complicado é realmente necessário para resolver esse problema de relase de recursos?
zinking 29/07

Respostas:

422

O padrão IDisposable recomendado está aqui . Ao programar uma classe que usa IDisposable, geralmente você deve usar dois padrões:

Ao implementar uma classe selada que não usa recursos não gerenciados, você simplesmente implementa um método Dispose como nas implementações normais da interface:

public sealed class A : IDisposable
{
    public void Dispose()
    {
        // get rid of managed resources, call Dispose on member variables...
    }
}

Ao implementar uma classe não lacrada, faça o seguinte:

public class B : IDisposable
{    
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            // get rid of managed resources
        }   
        // get rid of unmanaged resources
    }

    // only if you use unmanaged resources directly in B
    //~B()
    //{
    //    Dispose(false);
    //}
}

Observe que eu não declarei um finalizador em B; você só deve implementar um finalizador se tiver recursos não gerenciados reais para descartar. O CLR lida com objetos finalizáveis ​​de maneira diferente de objetos não finalizáveis, mesmo se SuppressFinalizefor chamado.

Portanto, você não deve declarar um finalizador, a menos que seja necessário, mas concede aos herdeiros um gancho para chamá-lo Disposee implementá-lo se eles usarem recursos não gerenciados diretamente:

public class C : B
{
    private IntPtr m_Handle;

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            // get rid of managed resources
        }
        ReleaseHandle(m_Handle);

        base.Dispose(disposing);
    }

    ~C() {
        Dispose(false);
    }
}

Se você não estiver usando recursos não gerenciados diretamente ( SafeHandlee os amigos não contarem, pois declaram seus próprios finalizadores), não implemente um finalizador, pois o GC lida com classes finalizáveis ​​de maneira diferente, mesmo se você suprimir o finalizador posteriormente. Observe também que, embora Bnão tenha um finalizador, ele ainda chamaSuppressFinalize para lidar corretamente com qualquer subclasse que implementa um finalizador.

Quando uma classe implementa a interface IDisposable, significa que em algum lugar existem alguns recursos não gerenciados que devem ser eliminados quando você terminar de usar a classe. Os recursos reais são encapsulados dentro das classes; você não precisa excluí-los explicitamente. Simplesmente chamar Dispose()ou agrupar a classe em using(...) {}garantirá que todos os recursos não gerenciados sejam eliminados conforme necessário.

thecoop
fonte
26
Eu concordo com o thecoop. Observe que você não precisa de um finalizador se estiver lidando apenas com recursos gerenciados (na verdade, NÃO deve tentar acessar objetos gerenciados de dentro do finalizador (exceto "this")), porque não há ordem garantida na qual o O GC limpará os objetos.Também, se você estiver usando o .Net 2.0 ou melhor, poderá (e deve) usar o SafeHandles para agrupar identificadores não gerenciados.Os identificadores de segurança reduzem bastante a necessidade de escrever finalizadores para as classes gerenciadas.blogs.msdn . com / bclteam / archive / 2005/03/16 / 396900.aspx
JMarsch
5
Eu acho que é melhor fazer uma chamada para MessageBox.Show ("Erro", + GetType (). Nome + "não descartado") no finalizador, pois o objeto descartável SEMPRE deve ser descartado e, se você não fizer isso, é melhor ser alertado para o fato o mais cedo possível.
21710 erikkallen
95
@erikkallen isso é uma piada? :)
Alex Norcliffe
2
pois é necessário um esforço computacional extra no CLR para acompanhar as aulas com finalizadores ativos. - A implementação de um finalizador faz com que isso aconteça. Chamar GC.SuppressFinalize significa que o Finalizador não deve ser chamado pelo tempo de execução. Ele ainda vai Gen2 independentemente. Não adicione um finalizador se não estiver lidando com recursos gerenciados. Modificadores de classe selados ou não selados são irrelevantes para esse ponto.
Ritch Melton
3
@Itch: citação? Isso não é necessariamente uma coisa ruim; se você estiver implementando IDisposable, é provável que demore um pouco. Você está salvando o CLR do esforço de copiá-lo de Gen0 -> Gen1 -> Gen2
thecoop
123

O padrão oficial a ser implementado IDisposableé difícil de entender. Eu acredito que este é melhor :

public class BetterDisposableClass : IDisposable {

  public void Dispose() {
    CleanUpManagedResources();
    CleanUpNativeResources();
    GC.SuppressFinalize(this);
  }

  protected virtual void CleanUpManagedResources() { 
    // ...
  }
  protected virtual void CleanUpNativeResources() {
    // ...
  }

  ~BetterDisposableClass() {
    CleanUpNativeResources();
  }

}

Uma solução ainda melhor é ter uma regra na qual você sempre deve criar uma classe de wrapper para qualquer recurso não gerenciado que você precise manipular:

public class NativeDisposable : IDisposable {

  public void Dispose() {
    CleanUpNativeResource();
    GC.SuppressFinalize(this);
  }

  protected virtual void CleanUpNativeResource() {
    // ...
  }

  ~NativeDisposable() {
    CleanUpNativeResource();
  }

}

Com SafeHandlee seus derivados, essas classes devem ser muito raras .

O resultado para classes descartáveis ​​que não lidam diretamente com recursos não gerenciados, mesmo na presença de herança, é poderoso: elas não precisam mais se preocupar com recursos não gerenciados . Eles serão simples de implementar e entender:

public class ManagedDisposable : IDisposable {

  public virtual void Dispose() {
    // dispose of managed resources
  }

}
Jordão
fonte
@Kyle: Obrigado! Eu também gosto :-) Há um acompanhamento aqui .
Jordão
4
Embora uma coisa que eu quero observar é que ela não impede que seja chamada pela segunda vez.
HuseyinUslu
5
@HuseyinUslu: esta é apenas a essência do padrão. Você certamente pode adicionar uma disposedbandeira e verificar em conformidade.
Jordão
2
@didibus: é uma simples questão de adicionar uma disposedbandeira, verifique-a antes de descartar e defina-a após descartar. Veja aqui a ideia. Você também deve verificar o sinalizador antes de qualquer método da classe. Faz sentido? É complicado?
Jordão
11
+1 em "Uma solução ainda melhor é ter uma regra na qual você sempre deve criar uma classe de wrapper para qualquer recurso não gerenciado que você precise manipular" . Eu deparei com isso em um complemento para o VLC e tenho usado desde então. Salva tantas dores de cabeça ...
Franz B.
37

Observe que qualquer implementação IDisposable deve seguir o padrão abaixo (IMHO). Desenvolvi esse padrão com base nas informações de vários "deuses" excelentes do .NET Framework Guidelines do .NET Framework (observe que o MSDN não segue isso por algum motivo!). As diretrizes de design do .NET Framework foram escritas por Krzysztof Cwalina (arquiteto do CLR na época) e Brad Abrams (acredito que o CLR Program Manager na época) e Bill Wagner ([C # efetivo] e [C # mais eficaz] (apenas faça uma procure-os no Amazon.com:

Observe que NUNCA deve implementar um Finalizador, a menos que sua classe contenha diretamente (não herda) recursos NÃO gerenciados. Depois de implementar um Finalizador em uma classe, mesmo que nunca seja chamado, é garantido que você terá uma coleção extra. Ele é automaticamente colocado na fila de finalização (que é executada em um único encadeamento). Além disso, uma observação muito importante ... todo o código executado dentro de um Finalizador (caso seja necessário implementar uma) DEVE ser seguro para threads E seguro para exceções! Coisas ruins acontecerão de outra forma ... (ou seja, comportamento indeterminado e, no caso de uma exceção, uma falha fatal irrecuperável do aplicativo).

O padrão que eu montei (e escrevi um trecho de código) é o seguinte:

#region IDisposable implementation

//TODO remember to make this class inherit from IDisposable -> $className$ : IDisposable

// Default initialization for a bool is 'false'
private bool IsDisposed { get; set; }

/// <summary>
/// Implementation of Dispose according to .NET Framework Design Guidelines.
/// </summary>
/// <remarks>Do not make this method virtual.
/// A derived class should not be able to override this method.
/// </remarks>
public void Dispose()
{
    Dispose( true );

    // This object will be cleaned up by the Dispose method.
    // Therefore, you should call GC.SupressFinalize to
    // take this object off the finalization queue 
    // and prevent finalization code for this object
    // from executing a second time.

    // Always use SuppressFinalize() in case a subclass
    // of this type implements a finalizer.
    GC.SuppressFinalize( this );
}

/// <summary>
/// Overloaded Implementation of Dispose.
/// </summary>
/// <param name="isDisposing"></param>
/// <remarks>
/// <para><list type="bulleted">Dispose(bool isDisposing) executes in two distinct scenarios.
/// <item>If <paramref name="isDisposing"/> equals true, the method has been called directly
/// or indirectly by a user's code. Managed and unmanaged resources
/// can be disposed.</item>
/// <item>If <paramref name="isDisposing"/> equals false, the method has been called by the 
/// runtime from inside the finalizer and you should not reference 
/// other objects. Only unmanaged resources can be disposed.</item></list></para>
/// </remarks>
protected virtual void Dispose( bool isDisposing )
{
    // TODO If you need thread safety, use a lock around these 
    // operations, as well as in your methods that use the resource.
    try
    {
        if( !this.IsDisposed )
        {
            if( isDisposing )
            {
                // TODO Release all managed resources here

                $end$
            }

            // TODO Release all unmanaged resources here



            // TODO explicitly set root references to null to expressly tell the GarbageCollector
            // that the resources have been disposed of and its ok to release the memory allocated for them.


        }
    }
    finally
    {
        // explicitly call the base class Dispose implementation
        base.Dispose( isDisposing );

        this.IsDisposed = true;
    }
}

//TODO Uncomment this code if this class will contain members which are UNmanaged
// 
///// <summary>Finalizer for $className$</summary>
///// <remarks>This finalizer will run only if the Dispose method does not get called.
///// It gives your base class the opportunity to finalize.
///// DO NOT provide finalizers in types derived from this class.
///// All code executed within a Finalizer MUST be thread-safe!</remarks>
//  ~$className$()
//  {
//     Dispose( false );
//  }
#endregion IDisposable implementation

Aqui está o código para implementar IDisposable em uma classe derivada. Observe que você não precisa listar explicitamente a herança de IDisposable na definição da classe derivada.

public DerivedClass : BaseClass, IDisposable (remove the IDisposable because it is inherited from BaseClass)


protected override void Dispose( bool isDisposing )
{
    try
    {
        if ( !this.IsDisposed )
        {
            if ( isDisposing )
            {
                // Release all managed resources here

            }
        }
    }
    finally
    {
        // explicitly call the base class Dispose implementation
        base.Dispose( isDisposing );
    }
}

Publiquei esta implementação no meu blog em: Como implementar corretamente o padrão de descarte

Dave Black
fonte
Alguém pode também adicionar um padrão para uma classe derivada (decorrente desta classe base)
akjoshi
3
@akjoshi - atualizei o padrão acima para incluir o código de uma classe descartável derivada. Também nota, NUNCA implementar um finalizador em uma classe derivada ...
Dave Black
3
A Microsoft parece gostar de definir o sinalizador "descartado" no final do método descartado, mas isso parece errado para mim. Chamadas redundantes para "Dispose" não devem fazer nada; embora normalmente não se espere que Dispose seja chamado recursivamente, essas coisas podem acontecer se alguém tentar Dispose um objeto que foi deixado em um estado inválido por uma exceção que ocorreu durante a construção ou alguma outra operação. Eu acho que usar um sinalizador de Interlocked.Exchangenúmero inteiro IsDisposedna função de invólucro não virtual seria mais seguro.
supercat 23/02/12
@DaveBlack: E se sua classe base não usar recursos não gerenciados, mas sua classe derivada usar? Você precisa implementar o Finalizer na classe derivada então? E se sim, como você sabe que a classe base ainda não a implementou se você não tem acesso à fonte?
Didier A.
@DaveBlack "Desenvolvi esse padrão com base em informações de vários excelentes" deuses "do .NET." Se um dos deuses era Jon Skeet, seguirei seu conselho.
Elisabeth
23

Concordo com o pm100 (e deveria ter dito isso explicitamente no meu post anterior).

Você nunca deve implementar IDisposable em uma classe, a menos que precise. Para ser muito específico, há cerca de 5 vezes em que você precisa / deve implementar o IDisposable:

  1. Sua classe contém explicitamente (ou seja, não por herança) quaisquer recursos gerenciados que implementam IDisposable e devem ser limpos assim que sua classe não for mais usada. Por exemplo, se sua classe contiver uma instância de um Stream, DbCommand, DataTable etc.

  2. Sua classe contém explicitamente quaisquer recursos gerenciados que implementam um método Close () - por exemplo, IDataReader, IDbConnection, etc. Observe que algumas dessas classes implementam IDisposable por ter Dispose () e um método Close ().

  3. Sua classe contém explicitamente um recurso não gerenciado - por exemplo, um objeto COM, ponteiros (sim, você pode usar ponteiros em C # gerenciado, mas eles devem ser declarados em blocos 'não seguros' etc.) No caso de recursos não gerenciados, verifique também se chame System.Runtime.InteropServices.Marshal.ReleaseComObject () no RCW Embora o RCW seja, em teoria, um invólucro gerenciado, ainda há contagem de referência em andamento.

  4. Se sua classe se inscrever em eventos usando referências fortes. Você precisa cancelar o registro / desanexar-se dos eventos. Sempre verifique se eles não são nulos antes de tentar cancelar o registro / desanexação deles !.

  5. Sua classe contém qualquer combinação dos itens acima ...

Uma alternativa recomendada para trabalhar com objetos COM e ter que usar Marshal.ReleaseComObject () é usar a classe System.Runtime.InteropServices.SafeHandle.

A BCL (Equipe da Biblioteca de Classes Base) tem uma boa publicação no blog aqui http://blogs.msdn.com/bclteam/archive/2005/03/16/396900.aspx

Uma observação muito importante a ser feita é que, se você estiver trabalhando com o WCF e limpando recursos, SEMPRE SEMPRE evite o bloco 'using'. Existem muitas postagens no blog e algumas no MSDN sobre por que essa é uma má idéia. Também publiquei aqui - Não use 'using ()' com um proxy WCF

Dave Black
fonte
3
Acredito que exista um quinto caso: se sua classe assina eventos usando referências fortes, você deve implementar IDisposable e cancelar o registro dos eventos no método Dispose.
Didier A.
Oi didibus. Sim você está correto. Eu esqueci disso. Modifiquei minha resposta para incluir isso como um caso. Obrigado.
21712 Dave
A documentação do MSDN para o padrão de descarte adiciona outro caso: "CONSIDERAM a implementação do padrão de descarte básico em classes que não possuem recursos não gerenciados ou objetos descartáveis, mas provavelmente possuem subtipos. Um ótimo exemplo disso é o System.IO .Stream class. Embora seja uma classe base abstrata que não possui nenhum recurso, a maioria de suas subclasses possui e, por isso, implementa esse padrão. "
Gonen I
12

Usando lambdas em vez de IDisposable.

Eu nunca fiquei empolgado com a ideia usando / IDisposable. O problema é que exige que o chamador:

  • sabem que eles devem usar IDisposable
  • lembre-se de usar 'using'.

Meu novo método preferido é usar um método de fábrica e um lambda

Imagine que eu quero fazer algo com um SqlConnection (algo que deve ser envolvido em um uso). Classicamente você faria

using (Var conn = Factory.MakeConnection())
{
     conn.Query(....);
}

Nova maneira

Factory.DoWithConnection((conn)=>
{
    conn.Query(...);
}

No primeiro caso, o chamador simplesmente não pôde usar a sintaxe de uso. No segundo caso, o usuário não tem escolha. Não existe um método que crie um objeto SqlConnection, o chamador deve chamar DoWithConnection.

DoWithConnection se parece com isso

void DoWithConnection(Action<SqlConnection> action)
{
   using (var conn = MakeConnection())
   {
       action(conn);
   }
}

MakeConnection agora é privado

pm100
fonte
2
Empacotar coisas em lambdas pode ser uma boa abordagem, mas tem limites. Não é tão ruim para situações em que, de fato, todos os consumidores de uma classe empregariam um bloco "using", mas impediria situações em que um método armazenaria um IDisposable em um campo de classe (diretamente ou em algo como um iterador )
Supercat
@ supercat, você pode argumentar que a proibição de armazenamento de recursos prejudiciais é uma coisa boa. O modelo de empréstimo que proponho aqui obriga você a ser enxuto com o uso do recurso
pm100
Pode ser uma coisa boa, mas também pode dificultar algumas operações razoáveis. Por exemplo, suponha que um tipo de leitor de banco de dados, em vez de implementar IEnumerable <T>, exponha um método DoForAll(Action<T>) where T:IComparable<T>, chamando o delegado indicado em cada registro. Dado dois desses objetos, os quais retornarão dados em ordem classificada, como um produziria todos os itens que existem em uma coleção, mas não no outro? Se os tipos forem implementados IEnumerable<T>, é possível executar uma operação de mesclagem, mas isso não funcionará DoForAll.
supercat
A única maneira de descobrir como mesclar duas DoForAllcoleções sem precisar primeiro copiar uma, em sua totalidade, em alguma outra estrutura, seria usar dois threads, o que seria um pouco mais hoggish de recursos do que simplesmente usar alguns IEnumerable e ter cuidado para liberá-los.
precisa
-1: boa resposta a uma pergunta que não foi feita. Esta seria uma grande resposta à pergunta "como eu faço consumo de IDisposable objetos mais fácil"
John Saunders
10

ninguém respondeu à pergunta sobre se você deve implementar o IDisposable, mesmo que não seja necessário.

Resposta curta: Não

Resposta longa:

Isso permitiria que um consumidor da sua classe usasse 'using'. A pergunta que eu faria é - por que eles fariam isso? A maioria dos desenvolvedores não usará 'using' a menos que saibam que precisam - e como sabem. Ou

  • seus obviuos eles da experiência (uma classe de soquete, por exemplo)
  • está documentado
  • eles são cautelosos e podem ver que a classe implementa IDisposable

Portanto, ao implementar o IDisposable, você está dizendo aos desenvolvedores (pelo menos alguns) que essa classe encerra algo que deve ser lançado. Eles usarão 'using' - mas há outros casos em que o uso não é possível (o escopo do objeto não é local); e eles terão que começar a se preocupar com a vida útil dos objetos nesses outros casos - eu me preocuparia com certeza. Mas isso não é necessário

Você implementa Idisposable para permitir que eles usem usando, mas eles não usarão, a menos que você peça.

Então não faça isso

pm100
fonte
11
Eu não entendo por que um desenvolvedor não usaria usar / dispor em um objeto implementando IDisposable (a menos que o programa esteja prestes a sair de qualquer maneira).
adrianm
11
o ponto é que um desenvolvedor precisaria escrever todas as chamadas para dispor em todos os caminhos de código que resultam na falta de referência a ele. Por exemplo, se eu colocar uma instância em um dicionário, ao excluir entradas do dicionário, devo chamar descarte. É um monte de aborrecimentos que não são necessários neste caso - o objeto não precisa ser descartado
Pm100
3
@ pm100 Re: desnecessariamente implementando IDisposable - Existe um artigo detalhado em codeproject.com/KB/dotnet/idisposable.aspx que discute alguns casos raros em que você pode pensar sobre isso (muito raro, tenho certeza). Resumindo: se você pode prever a necessidade de IDisposable no futuro ou em um objeto derivado, pense em implementar o IDisposable como um "não operacional" na sua classe base para evitar problemas de "fatiar" onde alguns objetos derivados exigem disposição e outros não.
22411 Kevin P. Rice
4
  1. Se você estiver usando outros objetos gerenciados que estão usando recursos não gerenciados, não é sua responsabilidade garantir que eles sejam finalizados. Sua responsabilidade é chamar Dispose nesses objetos quando Dispose é chamado no seu objeto e para por aí.

  2. Se sua classe não usa recursos escassos, não entendo por que você faria sua classe implementar IDisposable. Você só deve fazer isso se estiver:

    • Saiba que você terá recursos escassos em seus objetos em breve, mas não agora (e eu quero dizer que, como em "ainda estamos desenvolvendo, ele estará aqui antes de terminarmos", não como em "Acho que precisamos disso" ")
    • Usando recursos escassos
  3. Sim, o código que usa seu código deve chamar o método Dispose do seu objeto. E sim, o código que usa seu objeto pode usar usingcomo você mostrou.

  4. (2 novamente?) É provável que o WebClient use recursos não gerenciados ou outros recursos gerenciados que implementam IDisposable. A razão exata, no entanto, não é importante. O importante é que ele implemente o IDisposable e, portanto, você deve agir de acordo com esse conhecimento, descartando o objeto quando terminar, mesmo que o WebClient não utilize outros recursos.

Lasse V. Karlsen
fonte
4

Descarte o padrão:

public abstract class DisposableObject : IDisposable
{
    public bool Disposed { get; private set;}      

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

    ~DisposableObject()
    {
        Dispose(false);
    }

    private void Dispose(bool disposing)
    {
        if (!Disposed)
        {
            if (disposing)
            {
                DisposeManagedResources();
            }

            DisposeUnmanagedResources();
            Disposed = true;
        }
    }

    protected virtual void DisposeManagedResources() { }
    protected virtual void DisposeUnmanagedResources() { }
}

Exemplo de herança:

public class A : DisposableObject
{
    public Component components_a { get; set; }
    private IntPtr handle_a;

    protected override void DisposeManagedResources()
    {
        try
        {
          Console.WriteLine("A_DisposeManagedResources");
          components_a.Dispose();
          components_a = null;
        }
        finally
        { 
          base.DisposeManagedResources();
        }
    }

    protected override void DisposeUnmanagedResources()
    {
        try
        {
          Console.WriteLine("A_DisposeUnmanagedResources");
          CloseHandle(handle_a);
          handle_a = IntPtr.Zero;
        }
        finally
        { 
          base.DisposeUnmanagedResources();
        }
    }
}

public class B : A
{
    public Component components_b { get; set; }
    private IntPtr handle_b;

    protected override void DisposeManagedResources()
    {
        try
        {
          Console.WriteLine("B_DisposeManagedResources");
          components_b.Dispose();
          components_b = null;
        }
        finally
        { 
          base.DisposeManagedResources();
        }
    }

    protected override void DisposeUnmanagedResources()
    {
        try
        {
          Console.WriteLine("B_DisposeUnmanagedResources");
          CloseHandle(handle_b);
          handle_b = IntPtr.Zero;
        }
        finally
        { 
          base.DisposeUnmanagedResources();
        }
    }
}
Andrei Krasutski
fonte
4

Alguns aspectos de outra resposta estão levemente incorretos por 2 razões:

Primeiro,

using(NoGateway objNoGateway = new NoGateway())

na verdade é equivalente a:

try
{
    NoGateway = new NoGateway();
}
finally
{
    if(NoGateway != null)
    {
        NoGateway.Dispose();
    }
}

Isso pode parecer ridículo, pois o operador 'new' nunca deve retornar 'null', a menos que você tenha uma exceção OutOfMemory. Mas considere os seguintes casos: 1. Você chama um FactoryClass que retorna um recurso IDisposable ou 2. Se você possui um tipo que pode ou não herdar de IDisposable, dependendo de sua implementação - lembre-se de que eu vi o padrão IDisposable implementado incorretamente vezes em muitos clientes em que os desenvolvedores apenas adicionam um método Dispose () sem herdar de IDisposable (ruim, ruim, ruim). Você também pode ter o caso de um recurso IDisposable ser retornado de uma propriedade ou método (novamente ruim, ruim, ruim - não 'entregue seus recursos IDisposable)

using(IDisposable objNoGateway = new NoGateway() as IDisposable)
{
    if (NoGateway != null)
    {
        ...

Se o operador 'as' retornar nulo (ou propriedade ou método que retorna o recurso), e seu código no bloco 'using' proteger contra 'null', seu código não explodirá ao tentar chamar Dispose em um objeto nulo por causa de a verificação nula 'incorporada'.

A segunda razão pela qual sua resposta não é precisa é o seguinte stmt:

Um finalizador é chamado ao GC destruindo seu objeto

Primeiro, a finalização (assim como o próprio GC) não é determinística. O CLR determina quando chamará um finalizador. ou seja, o desenvolvedor / código não tem idéia. Se o padrão IDisposable for implementado corretamente (como postamos acima) e o GC.SuppressFinalize () tiver sido chamado, o Finalizador NÃO será chamado. Esse é um dos grandes motivos para implementar corretamente o padrão corretamente. Como existe apenas um encadeamento do Finalizer por processo gerenciado, independentemente do número de processadores lógicos, é possível degradar facilmente o desempenho fazendo backup ou até interrompendo o encadeamento do Finalizer, esquecendo de chamar GC.SuppressFinalize ().

Publiquei uma implementação correta do Dispose Pattern no meu blog: Como implementar corretamente o Dispose Pattern

Dave Black
fonte
2
Você tem certeza sobre escrever NoGateway = new NoGateway();e NoGateway != null?
Cœur
11
Referia-se a stackoverflow.com/a/898856/3195477 ? Agora não há nenhuma resposta postada com o nome 'Icey'
UuDdLrLrSs 14/11
@DaveInCaz parece que está correto. Não vejo 'Icey' em lugar nenhum, mas o contexto da minha resposta parece estar direcionado para a resposta fornecida pelo seu link acima. Talvez ele tenha mudado seu nome de usuário?
Dave Black
@DaveBlack legal, obrigado. Acabei de editar isso no texto.
UuDdLrLrSs
2

1) O WebClient é um tipo gerenciado, portanto você não precisa de um finalizador. O finalizador é necessário no caso de seus usuários não Dispose () da sua classe NoGateway e o tipo nativo (que não é coletado pelo GC) precisa ser limpo depois. Nesse caso, se o usuário não chamar Dispose (), o WebClient contido será descartado pelo GC logo após o NoGateway.

2) Indiretamente sim, mas você não deve se preocupar com isso. Seu código está correto como está e você não pode impedir que seus usuários se esqueçam de Dispose () com muita facilidade.

Jesse C. Slicer
fonte
2

Padrão do msdn

public class BaseResource: IDisposable
{
   private IntPtr handle;
   private Component Components;
   private bool disposed = false;
   public BaseResource()
   {
   }
   public void Dispose()
   {
      Dispose(true);      
      GC.SuppressFinalize(this);
   }
   protected virtual void Dispose(bool disposing)
   {
      if(!this.disposed)
      {        
         if(disposing)
         {
            Components.Dispose();
         }         
         CloseHandle(handle);
         handle = IntPtr.Zero;
       }
      disposed = true;         
   }
   ~BaseResource()      
   {      Dispose(false);
   }
   public void DoSomething()
   {
      if(this.disposed)
      {
         throw new ObjectDisposedException();
      }
   }
}
public class MyResourceWrapper: BaseResource
{
   private ManagedResource addedManaged;
   private NativeResource addedNative;
   private bool disposed = false;
   public MyResourceWrapper()
   {
   }
   protected override void Dispose(bool disposing)
   {
      if(!this.disposed)
      {
         try
         {
            if(disposing)
            {             
               addedManaged.Dispose();         
            }
            CloseHandle(addedNative);
            this.disposed = true;
         }
         finally
         {
            base.Dispose(disposing);
         }
      }
   }
}
devnull
fonte
1
using(NoGateway objNoGateway = new NoGateway())

é equivalente a

try
{
    NoGateway = new NoGateway();
}

finally
{
    NoGateway.Dispose();
}

Um finalizador é chamado para o GC destruindo seu objeto. Isso pode ocorrer em um momento totalmente diferente do que quando você sai do seu método. O Dispose of IDisposable é chamado imediatamente depois que você sai do bloco using. Portanto, o padrão geralmente é usar o recurso para liberar recursos imediatamente após você não precisar mais deles.

Daniel Fabian
fonte
11
Um finalizador não é chamado pelo GC que está destruindo o objeto. Se "Finalizar" for substituído, quando o GC tiver destruído o objeto , ele será colocado em uma fila de objetos que precisam de finalização, criando temporariamente uma forte referência a ele e - pelo menos temporariamente - "ressuscitando" o objeto .
Supercat
-5

Pelo que sei, é altamente recomendável NÃO usar o Finalizador / Destrutor:

public ~MyClass() {
  //dont use this
}

Principalmente, isso se deve ao não saber quando ou se será chamado. O método de descarte é muito melhor, especialmente se você estiver usando ou descartado diretamente.

usando é bom. use-o :)

Nic Wise
fonte
2
Você deve seguir o link na resposta do thecoop. Sim, usar / Dispose é melhor, mas uma classe Disposable definitivamente deve implementar as duas.
Henk Holterman
Interessante, todos os documentos que li da microsoft - por exemplo, as diretrizes de design da estrutura - dizem: NUNCA use um destruidor. Sempre use IDisposable.
Nic Wise #
5
Apenas faça uma distinção entre usar uma classe e escrevê- la, e leia-a novamente.
Henk Holterman
stackoverflow.com/questions/2605412/… pode ajudar #
Alex Nolasco