Estou estudando o padrão Dispose em C#, e entendo que o método Dispose é usado quando há recursos não gerenciados. Por exemplo, se eu tiver um recurso não gerenciado em FirstClass, e FirstClass estiver contido em uma classe Node, preciso implementar o método Dispose para ambos, certo?
O que mais não entendo é se preciso chamar GC.SuppressFinalize(this); em cada método Dispose.
GC.SuppressFinalize(this); instrui o coletor de lixo a não finalizar, mas isso significa que GC.SuppressFinalize(this); só é necessário no método Dispose se a classe tiver um finalizador? Se for esse o caso, acho que seria necessário tanto na classe Node quanto na BaseClass. No entanto, como a classe Node também tem membros gerenciados, é aceitável suprimir o finalizador nesse caso?
Estou confuso porque há classes aninhadas com métodos Dispose.
public class Node : IDisposable
{
public Node Next { get; set; } = null;
public Node Child { get; set; } = null;
public FirstClass CppInstance { get; set; } = null;
public Node()
{
CppInstance = new FirstClass();
}
~Node()
{
this.Dispose();
}
public void Dispose()
{
this.CppInstance?.Dispose();
}
}
public class FirstClass : BaseClass
{
private List<String> _nameList = [];
public FirstClass()
{
CreateCppData( out _instance );// C++ Function create
_nameList.Add("A");
_nameList.Add("B");
_nameList.Add("C");
}
protected override void Dispose( Boolean disposing )
{
if( !disposedValue )
{
if( disposing )
{
// Managed resource release
_nameList.Clear();
_nameList = null;
}
// Unmanaged resource release
DeleteCppData( ref _instance ); // C++ Function delete
disposedValue = true;
}
}
}
public class BaseClass :IDisposable
{
protected Boolean disposedValue = false;
protected IntPtr _instance; // ● Unmanaged Data
~BaseClass()
{
Dispose( false );
}
protected virtual void Dispose( Boolean disposing )
{
}
public void Dispose()
{
this.Dispose( disposing: true );
GC.SuppressFinalize( this );
}
public virtual void Run()
{
}
}
C# Eu reescrevi com base no conselho.
public class Node : IDisposable
{
public Node Next { get; set; } = null;
public Node Child { get; set; } = null;
public FirstClass FirstClassInstance { get; set; } = null;
protected Boolean disposedValue = false;
public Node()
{
FirstClassInstance = new FirstClass();
}
~Node()
{
this.Dispose( false );
}
public void Dispose()
{
this.Dispose( true );
GC.SuppressFinalize( this );
}
protected virtual void Dispose( Boolean disposing )
{
if( !disposedValue )
{
this.FirstClassInstance?.Dispose();
this.FirstClassInstance = null;
disposedValue = true;
}
}
}
public class FirstClass : BaseClass
{
private List<String> _nameList = [];
public FirstClass()
{
_nameList.Add("A");
_nameList.Add("B");
_nameList.Add("C");
}
protected override void Dispose( Boolean disposing )
{
if( !disposedValue )
{
if( disposing )
{
// Managed resource release
_nameList.Clear();
_nameList = null;
}
base.Dispose( disposing ); // BaseClass Dispose
}
}
protected override void CreateCppInstance()
{
CreateCppData( out _instance ); // C++ Function create
}
protected override void DeleteCppInstance()
{
DeleteCppData( ref _instance ); // C++ Function delete
}
}
public class BaseClass :IDisposable
{
protected Boolean disposedValue = false;
protected IntPtr _instance; // ● Unmanaged Data
public BaseClass()
{
CreateCppInstance();
}
~BaseClass()
{
Dispose( false );
}
protected virtual void CreateCppInstance()
{
throw new Exception();
}
protected virtual void DeleteCppInstance()
{
throw new Exception();
}
protected override void Dispose( Boolean disposing )
{
if( !disposedValue )
{
if( disposing )
{
// Managed resource release
}
// Unmanaged resource release
DeleteCppInstance();
disposedValue = true;
}
}
public void Dispose()
{
this.Dispose( disposing: true );
GC.SuppressFinalize( this );
}
}
A discussão nesta questão Dispose() para limpar recursos gerenciados? aborda isso indiretamente.
A verdadeira questão aqui é
Geralmente, SIM .
O caso de uso geral para suprimir o finalizador em discard é quando seu finalizador também chama
Dispose()
, o que é uma prática padrão para que tenhamos que escrever nossa lógica de limpeza de recursos apenas uma vez. Suprimir o finalizador impede a reentrada na lógica Dispose que já foi executada.Se sua classe não implementar um finalizador, então você não se incomodaria em suprimir o finalizador. No caso de herança, você pode querer suprimir o finalizador se não tiver controle da implementação da classe base e souber que quer suprimir a lógica, presumivelmente porque elevou a mesma lógica para seu método de descarte local. Mas isso seria um design ruim e é apenas uma solução alternativa avançada que você implementa após identificar problemas em tempo de execução, não deve ser seu design padrão. Seria melhor para sua implementação local limpar apenas os recursos que sua classe local possui e chamar a implementação base de descarte de sua classe local para limpar quaisquer recursos na base. Se a implementação base precisar suprimir finalize, então deve.
Dispose()
, você normalmente suprimirá o finalizador na lógica de descarte.