Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Observação
Esse conteúdo é reimpresso por permissão da Pearson Education, Inc. das Diretrizes de Design da Estrutura: Convenções, Idiomas e Padrões para Bibliotecas .NET Reutilizáveis, 2ª Edição. Essa edição foi publicada em 2008, e desde então o livro foi totalmente revisado na terceira edição. Algumas das informações nesta página podem estar desatualizadas.
Todos os programas adquirem um ou mais recursos do sistema, como memória, identificadores do sistema ou conexões de banco de dados, durante a execução. Os desenvolvedores precisam ter cuidado ao usar esses recursos do sistema, pois eles devem ser liberados após serem adquiridos e usados.
O CLR fornece suporte para o gerenciamento automático de memória. A memória gerenciada (memória alocada usando o operador new
C#) não precisa ser liberada explicitamente. Ele é liberado automaticamente pelo coletor de lixo (GC). Isso libera os desenvolvedores da tarefa entediante e difícil de liberar memória e tem sido um dos principais motivos para a produtividade sem precedentes proporcionada pelo .NET Framework.
Infelizmente, a memória gerenciada é apenas um dos muitos tipos de recursos do sistema. Recursos diferentes da memória gerenciada ainda precisam ser liberados explicitamente e são chamados de recursos não gerenciados. O GC não foi especificamente projetado para gerenciar esses recursos não gerenciados, o que significa que a responsabilidade de gerenciar recursos não gerenciados está nas mãos dos desenvolvedores.
O CLR fornece alguma ajuda na liberação de recursos não gerenciados. System.Object declara um método Finalize virtual (também chamado de finalizador) que é chamado pelo GC antes que a memória do objeto seja recuperada pelo GC e possa ser substituído para liberar recursos não gerenciados. Os tipos que substituem o finalizador são chamados de tipos finalizáveis.
Embora os finalizadores sejam eficazes em alguns cenários de limpeza, eles têm duas desvantagens significativas:
O finalizador é chamado quando o GC detecta que um objeto está qualificado para coleção. Isso acontece em algum período indeterminado depois que o recurso não é mais necessário. O atraso entre quando o desenvolvedor poderia ou gostaria de liberar o recurso e o momento em que o recurso é realmente liberado pelo finalizador pode ser inaceitável em programas que adquirem muitos recursos escassos (recursos que podem ser facilmente esgotados) ou em casos em que os recursos são caros para manter em uso (por exemplo, grandes buffers de memória não gerenciados).
Quando o CLR precisa chamar um finalizador, ele deve adiar a coleta da memória do objeto até a próxima rodada de coleta de lixo (os finalizadores são executados entre coleções). Isso significa que a memória do objeto (e todos os objetos aos quais ele se refere) não será liberada por um período maior de tempo.
Portanto, depender exclusivamente de finalizadores pode não ser apropriado em muitos cenários quando é importante recuperar recursos não gerenciados o mais rápido possível, ao lidar com recursos escassos ou em cenários de alto desempenho em que a sobrecarga de finalização do GC adicionada é inaceitável.
A Estrutura fornece a System.IDisposable interface que deve ser implementada para fornecer ao desenvolvedor uma maneira manual de liberar recursos não gerenciados assim que eles não forem necessários. Ele também fornece o GC.SuppressFinalize método que pode informar ao GC que um objeto foi descartado manualmente e não precisa mais ser finalizado, nesse caso, a memória do objeto pode ser recuperada anteriormente. Os tipos que implementam a IDisposable
interface são chamados de tipos descartáveis.
O Descarte Padrão destina-se a padronizar o uso e a implementação de finalizadores e da interface IDisposable
.
A motivação principal para o padrão é reduzir a complexidade da implementação dos métodos Finalize e Dispose. A complexidade decorre do fato de que os métodos compartilham alguns, mas não todos os caminhos de código (as diferenças são descritas posteriormente no capítulo). Além disso, há motivos históricos para alguns elementos do padrão relacionados à evolução do suporte de linguagem para o gerenciamento de recursos determinístico.
✓ Implemente o Padrão básico de descarte em tipos que contêm instâncias de tipos descartáveis. Consulte a seção Padrão de Descarte Básico para obter detalhes sobre o padrão básico.
Se um tipo for responsável pelo tempo de vida de outros objetos descartáveis, os desenvolvedores também precisarão de uma maneira de descartá-los. Usar o método do Dispose
contêiner é uma maneira conveniente de tornar isso possível.
✓ Implemente o Padrão de Descarte Básico e implemente um finalizador para tipos que contêm recursos que precisam ser liberados explicitamente e que não têm finalizadores.
Por exemplo, o padrão deve ser implementado em tipos que armazenam buffers de memória não gerenciados. A seção Tipos Finalizáveis discute as diretrizes relacionadas à implementação de finalizadores.
✓ CONSIDERE a implementação do Padrão básico de descarte em classes que não contêm recursos não gerenciados ou objetos descartáveis, mas provavelmente têm subtipos que o fazem.
Um ótimo exemplo disso é a System.IO.Stream classe. Embora seja uma classe base abstrata que não contém recursos, a maioria de suas subclasses o fazem e, por isso, implementa esse padrão.
Padrão básico de descarte
A implementação básica do padrão envolve implementar a System.IDisposable
interface e declarar o Dispose(bool)
método que implementa toda a lógica de limpeza de recursos a ser compartilhada entre o Dispose
método e o finalizador opcional.
O exemplo a seguir mostra uma implementação simples do padrão básico:
public class DisposableResourceHolder : IDisposable {
private SafeHandle resource; // handle to a resource
public DisposableResourceHolder() {
this.resource = ... // allocates the resource
}
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing) {
if (disposing) {
if (resource!= null) resource.Dispose();
}
}
}
O parâmetro disposing
booliano indica se o método foi invocado da IDisposable.Dispose
implementação ou do finalizador. A Dispose(bool)
implementação deve verificar o parâmetro antes de acessar outros objetos de referência (por exemplo, o campo de recurso no exemplo anterior). Esses objetos só devem ser acessados quando o método é chamado da IDisposable.Dispose
implementação (quando o disposing
parâmetro é igual a true). Se o método for invocado do finalizador (disposing
é falso), outros objetos não deverão ser acessados. O motivo é que os objetos são finalizados em uma ordem imprevisível e, portanto, eles, ou qualquer uma de suas dependências, podem já ter sido finalizados.
Além disso, esta seção se aplica a classes com uma base que ainda não implementa o Padrão Dispose. Se você estiver herdando de uma classe que já implementa o padrão, basta substituir o Dispose(bool)
método para fornecer lógica de limpeza de recursos adicional.
✓ Declare um protected virtual void Dispose(bool disposing)
método para centralizar toda a lógica relacionada à liberação de recursos não gerenciados.
Toda a limpeza de recursos deve ocorrer nesse método. O método é chamado tanto do finalizador quanto do método IDisposable.Dispose
. O parâmetro será falso se for invocado de dentro de um finalizador. Ele deve ser usado para garantir que qualquer código em execução durante a finalização não esteja acessando outros objetos finalizáveis. Os detalhes da implementação de finalizadores são descritos na próxima seção.
protected virtual void Dispose(bool disposing) {
if (disposing) {
if (resource!= null) resource.Dispose();
}
}
✓ Implemente a IDisposable
interface simplesmente chamando Dispose(true)
seguido por GC.SuppressFinalize(this)
.
A chamada a SuppressFinalize
só deverá ocorrer se Dispose(true)
for executada com êxito.
public void Dispose(){
Dispose(true);
GC.SuppressFinalize(this);
}
X NÃO torne o método sem Dispose
parâmetro virtual.
O Dispose(bool)
método é aquele que deve ser substituído por subclasses.
// bad design
public class DisposableResourceHolder : IDisposable {
public virtual void Dispose() { ... }
protected virtual void Dispose(bool disposing) { ... }
}
// good design
public class DisposableResourceHolder : IDisposable {
public void Dispose() { ... }
protected virtual void Dispose(bool disposing) { ... }
}
X NÃO declare nenhuma sobrecarga do método Dispose
além de Dispose()
e Dispose(bool)
.
Dispose
deve ser considerada uma palavra reservada para ajudar a codificar esse padrão e evitar confusão entre implementadores, usuários e compiladores. Alguns idiomas podem optar por implementar automaticamente esse padrão em determinados tipos.
✓ Permitir que o Dispose(bool)
método seja chamado mais de uma vez. O método pode optar por não fazer nada após a primeira chamada.
public class DisposableResourceHolder : IDisposable {
bool disposed = false;
protected virtual void Dispose(bool disposing) {
if (disposed) return;
// cleanup
...
disposed = true;
}
}
X AVOID gerando uma exceção de dentro Dispose(bool)
, exceto em situações críticas em que o processo de contenção foi corrompido (vazamentos, estado compartilhado inconsistente etc.).
Os usuários esperam que uma chamada para Dispose
não gere uma exceção.
Se Dispose
puder gerar uma exceção, a execução da lógica de limpeza adicional do bloco finally não acontecerá. Para contornar isso, o usuário precisaria encapsular cada chamada para Dispose
(dentro do bloco finally!) em um bloco try, o que leva a rotinas de limpeza muito complexas. Se estiver executando um Dispose(bool disposing)
método, nunca gere uma exceção se a eliminação for falsa. Isso encerrará o processo se estiver em execução dentro de um contexto de finalizador.
✓ LANCE uma ObjectDisposedException a partir de qualquer membro que não possa ser utilizado após o objeto ter sido descartado.
public class DisposableResourceHolder : IDisposable {
bool disposed = false;
SafeHandle resource; // handle to a resource
public void DoSomething() {
if (disposed) throw new ObjectDisposedException(...);
// now call some native methods using the resource
...
}
protected virtual void Dispose(bool disposing) {
if (disposed) return;
// cleanup
...
disposed = true;
}
}
✓ CONSIDERE fornecer o método Close()
, além do Dispose()
, se "close" é a terminologia padrão na área.
Ao fazer isso, é importante que você torne a implementação Close
idêntica a Dispose
e considere implementar o método IDisposable.Dispose
explicitamente.
public class Stream : IDisposable {
IDisposable.Dispose() {
Close();
}
public void Close() {
Dispose(true);
GC.SuppressFinalize(this);
}
}
Tipos finalizáveis
Tipos finalizáveis são tipos que estendem o Padrão de Descarte Básico substituindo o finalizador e fornecendo o caminho do código de finalização no Dispose(bool)
método.
Os finalizadores são notoriamente difíceis de implementar corretamente, principalmente porque você não pode fazer certas suposições (normalmente válidas) sobre o estado do sistema durante a execução. As diretrizes a seguir devem ser levadas em consideração.
Observe que algumas das diretrizes se aplicam não apenas ao Finalize
método, mas a qualquer código chamado de um finalizador. No caso do Padrão de Descarte Básico definido anteriormente, isso significa a lógica executada dentro de Dispose(bool disposing)
quando o parâmetro disposing
é falso.
Se a classe base já for capaz de ser finalizada e implementar o Padrão Básico de Descarte, você não deverá sobrescrever Finalize
novamente. Em vez disso, você deve substituir o método Dispose(bool)
para fornecer uma lógica adicional de limpeza de recursos.
O código a seguir mostra um exemplo de um tipo finalizável:
public class ComplexResourceHolder : IDisposable {
private IntPtr buffer; // unmanaged memory buffer
private SafeHandle resource; // disposable handle to a resource
public ComplexResourceHolder() {
this.buffer = ... // allocates memory
this.resource = ... // allocates the resource
}
protected virtual void Dispose(bool disposing) {
ReleaseBuffer(buffer); // release unmanaged memory
if (disposing) { // release other disposable objects
if (resource!= null) resource.Dispose();
}
}
~ComplexResourceHolder() {
Dispose(false);
}
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
}
X AVOID tornando os tipos finalizáveis.
Considere cuidadosamente qualquer caso em que você pense que um finalizador é necessário. Há um custo real associado a instâncias com finalizadores, tanto do ponto de vista de desempenho quanto de complexidade de código. Prefira usar wrappers de recursos, como SafeHandle, para encapsular recursos não gerenciados sempre que possível. Nesse caso, um finalizador se torna desnecessário porque o próprio wrapper é responsável pela limpeza de seus recursos.
X NÃO torne os tipos de valor finalizáveis.
Somente os tipos de referência realmente são finalizados pelo CLR e, portanto, qualquer tentativa de colocar um finalizador em um tipo de valor será ignorada. Os compiladores C# e C++ impõem essa regra.
✓ Torne um tipo finalizável se o tipo for responsável por liberar um recurso não gerenciado que não tenha seu próprio finalizador.
Ao implementar o finalizador, basta chamar Dispose(false)
e colocar toda a lógica de limpeza de recursos dentro do Dispose(bool disposing)
método.
public class ComplexResourceHolder : IDisposable {
~ComplexResourceHolder() {
Dispose(false);
}
protected virtual void Dispose(bool disposing) {
...
}
}
✓ Implemente o Padrão de Descarte Básico em cada tipo finalizável.
Isso fornece aos usuários do tipo um meio de executar explicitamente a limpeza determinística desses mesmos recursos para os quais o finalizador é responsável.
X NÃO acesse nenhum objeto finalizável no caminho do código do finalizador, pois há um risco significativo de que eles já tenham sido finalizados.
Por exemplo, um objeto finalizável A que tem uma referência a outro objeto finalizável B não pode usar B de forma confiável no finalizador de A ou vice-versa. Os finalizadores são chamados em uma ordem aleatória (exceto em caso de uma garantia de ordenação fraca para finalização crítica).
Além disso, lembre-se de que os objetos armazenados em variáveis estáticas serão coletados em determinados pontos durante um descarregamento de domínio do aplicativo ou ao sair do processo. Acessar uma variável estática que se refere a um objeto finalizável (ou chamar um método estático que pode usar valores armazenados em variáveis estáticas) pode não ser seguro se Environment.HasShutdownStarted retornar true.
✓ Torne seu Finalize
método protegido.
Os desenvolvedores de C#, C++e VB.NET não precisam se preocupar com isso, pois os compiladores ajudam a impor essa diretriz.
X DO NOT permite que as exceções escapem da lógica do finalizador, exceto por falhas críticas do sistema.
Se uma exceção for gerada de um finalizador, o CLR desligará todo o processo (a partir do .NET Framework versão 2.0), impedindo que outros finalizadores sejam executados e os recursos sejam liberados de maneira controlada.
✓ CONSIDERE criar e usar um objeto finalizável crítico (um tipo com uma hierarquia de tipos que contém CriticalFinalizerObject) para situações em que um finalizador deve ser executado absolutamente, mesmo em caso de descarregamento forçado do domínio do aplicativo e interrupções de thread.
Partes © 2005, 2009 Microsoft Corporation. Todos os direitos reservados.
Reimpresso por permissão da Pearson Education, Inc. da Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition por Krzysztof Cwalina e Brad Abrams, publicado em 22 de outubro de 2008 pela Addison-Wesley Professional como parte da Microsoft Windows Development Series.