Podemos classificar os recursos utizados em .NET em duas categorias: gerenciados e não gerenciados.
Recursos gerenciados são desalocados automaticamente. Por outro lado, recursos não gerenciados dependem de uma estratégia adequada de desalocação explícita, geralmente implementada através do Dispose Pattern.
IMPORTANTE: É comum, entre os programadores .NET, associar do método Dispose()
com liberação de memória. Entretanto, é importante destacar que, frequentemente, este não é o caso. Como iremos demonstrar, recursos não gerenciados podem ser de outros tipos.
O que são recursos gerenciados?
Quando falamos de recursos gerenciados estamos tratando, na prática, da memória gerenciada.
A memória gerenciada é aquela associada a objetos na heap que, quando não tem mais nenhuma referênica no código, são coletados, eventualmente, pelo Garbage Collector. Também estamos falando dos valores armazenados na stack que são desalocados quando há um descarte de um stack frame.
O que são recursos não gerenciados
Recursos não gerenciados são todos os recursos alocados pela aplicação, exceto a memória gerenciada.
Estão entre os recursos não gerenciados a memória alocada que não é controlada pelo Garbage Collector, como aquela alocada em rotinas escritas em C++. Também são recursos não gerenciados os handles de arquivos, sockets e outros recursos de rede, conexões com banco de dados, objetos GDI, etc.
Recursos não gerenciados geralmente são liberados através do método Dispose.
O método Dispose
Objetos que utilizam recursos não gerenciados geralmente implementam alguma estratégia de desalocação explícita desses objetos, geralmente através do método Dispose.
É importante lembrar que objetos não gerenciados não são desalocados automaticamente em .NET. Se um objeto implementa o método Dispose, então, este deve ser evocado explicitamente no código (ou implicitamente, através da utilização da instrução using).
Objetos que implementam o método Dispose, definido na interface IDisposable, deveriam implementar o Dispose Pattern.
Dispose Pattern
O Dispose Pattern existe para garantir que objetos que utilizam recursos não gerenciados irão os descartar quando forem coletados pelo GC, mesmo que não tenham tido sua implementação para o método Dispose executada.
Abaixo, podemos ver um exemplo de implementação recomendada do dispose pattern proposta pelo Visual Studio:
O que o código relacionado ao Dispose Pattern faz?
Quando implementamos o Dispose Pattern, no método Dispose()
executado sem parâmetros, chamamos o método Dispose(true)
que, realizará as operações de liberação de recursos não gerenciados e também fará uma chamada ao método SuppressFinalize()
do garbage collector, impedindo que este execute o finalizador para o objeto quando ele for coletado.
Se o programador esquecer de chamar o método Dispose explicitamente, então, eventualmente, o GC irá executar o finalizador e executará o Dispose(false).
NOTA: O método Finalize (finalizador) é executado pelo Garbage Collector sempre antes de descartar um objeto da memória gerenciada. Por isso, é uma boa ideia chamar o método Dispose no finalizador para garantir que recursos gerenciados serão liberados, mesmo quando o programador esquecer de chamar o método Dispose.
Cuidados ao definir código para um finalizador
Como indicamos, os finalizadores permitem definir código que será executado quando uma instância é descartada pelo GC. Entretanto, é bom lembrar que o GC opera de maneira não determinística, não sendo possível determinar quando o código do finalizador será, finalmente, executado. Assim, não podemos determinar, também, quando o recurso não gerenciado será finalmente liberado (no caso do programador cliente “esquecer” de executar o Dispose explicitamente).
Outro aspecto importante é que, por objetivar performance, o GC não descarta objetos, em Gen #0, que possuam finalizadores promovendo-os imediatamente para Gen #1. O que aumenta o custo do descarte.
NOTA: O método SuppressFinalize()
“avisa” o GC que não é necessário executar o código do finalizador antes da coleta autorizando seu descarte em Gen #0,
Concluindo
É muito importante que saibamos identificar rapidamente recursos gerenciados e recursos não gerenciados. Recursos não gerenciados frequentemente não tem relação direta com memória alocada.
Sempre que utilizamos recursos não gerenciados, temos que projetar o descarte desses, preferencialmente de forma explícita pois ela não ocorrerá automaticamente.
Se um tipo contiver entre seus atributos um recurso não gerenciado, então deverá implementar a interface IDisposable.
A implementação de IDisposable respeitando o Dispose Pattern garante que recursos não gerenciados sejam sempre descartados.
Objetos que implementam finalizadores são mais custosos para liberar. Por isso, devemos lembrar de chamar o método Dispose explicitamente sempre.
Bem bom o POST! Curti!!
Só lembrando que *ref struct* em C# 8 permite ter um método *Dispose()*, sem implementar explicitamente a interface *IDisposable*, o que é uma espécie de *duck typing* da linguagem (não disponível para o programador criar seu próprio). E neste caso não envolve GC porque este tipo só pode ser alocado na *stack*.
Olá, tudo bem?
Achei muito interessante o post, mas fiquei com uma dúvida, quanto ao texto abaixo:
NOTA: O método Finalize (finalizador) é executado pelo Garbage Collector sempre antes de descartar um objeto da memória gerenciada.
Quem é esse método Finalize(finalizador)?
Quando vc diz finalizador, está se referindo ao destrutor da classe que tem a chamada para Dispose(false)?
Muito obrigado
Em C# não há destrutores. O método que “parece” o destrutor é, na prática, o Finalizer
Tenho uma classe, um Value Objetc (Lancamento) e o mesmo como atributo tem 3 List que sao manipulados por suas propriedades. Sempre q crio uma instancia desse VO eu popilos os atributos de tipo List ( public class LancamentosBloqueios : List, ILancamentos, IDisposable) e sempre que crio este VO eu carrego o mesmo para memoria. Minha duvida e devo implementar o IDisposabel na classe VO e no seu metodo dispose, chamar dispose para o LancamentosBloqueios?