About poor Dispose, put in a word (part 2)
After the publication of my previous record on Dispose, several acute questions surfaced in the discussion that required separate consideration.
In short, they boil down to “why so complicated?” and "how should posterity liberate their memory?"
And indeed. There are two implementations. Simple:
First, I will answer the second question: the proposed option for freeing resources must be known if you inherit from standard classes that have already implemented the IDisposable interface. An example is System.Windows.Forms.UserControl, which already has a Dispose method implemented.
A simple case: you spawned your control from UserControl. And your control owns an unmanaged resource. For example, he knows how to display an image using OpenGL, and for this he needs to control the rendering context. And not only manage, but also correctly release it. In this case, by overriding the Dispose method on the parent class, you interfere with the resource deallocation scheme, which is obviously bad.
In this case, it is possible to solve the problem by interacting with the resource release mechanism that MS proposed. And their solution looks quite simple:
1) There is a method for manual cleaning “right now and immediately”: Dispose ()
2) There is a method for cleaning “it's time to remove garbage” (called from GC): destructor
3) Both methods must perform the cleanup, which is carried out in a separate method: Dispose (bool)
The Dispose (bool) method, which performs the cleanup, receives an input that allows you to distinguish the cleanup from “right now and immediately” from “it's time clean up the trash. " What for? And because your class can own managed member variables that own unmanaged resources. And so that they can free unmanaged resources, they also need to call Dispose. In contrast to the situation when GC is engaged in cleaning. In this case, there were no demands to release resources “right now and immediately” and you can rely on GC.
In the implementation proposed for the case of inheritance from MSDN, there is one more peculiarity: there is no disposed flag variable. Those. no protection against multiple Dispose calls. Which is not very pretty. There is a slightly different implementation suggested in this article:
In short, they boil down to “why so complicated?” and "how should posterity liberate their memory?"
And indeed. There are two implementations. Simple:
DBConnection conn = new DBConnection();
try
{
conn.Open();
//...
}
finally
{
conn.Close();
}
* This source code was highlighted with Source Code Highlighter.And complicated:public class MyResource: IDisposable
{
private bool disposed = false;
//реализация интерфейса IDisposable
//не делайте эту функцию виртуальной
//и не перекрывайте в потомках
public void Dispose()
{
Dispose(true); //освобождение вызвали вручную
GC.SuppressFinalize(this); //не хотим, чтобы GC вызвал деструктор
}
//деструктор
~MyClass()
{
Dispose(false); //освобождения ресурсов потребовал GC вызвав деструтор
}
//освобождаем ресурсы
private void Dispose(bool manual)
{
if (!this.disposed)
{
if (manual)
{
//освобождаем managed ресурсы
//...
//иными словами - вызываем метод Dispose
//для всех managed член-переменных класса
//это нужно делать только для ручного вызова Dispose,
//потому что в другом случае случае Dispose для них вызовет GC
}
//освобождаем unmanaged ресурсы
//...
disposed = true;
}
}
}
* This source code was highlighted with Source Code Highlighter.Moreover, a complex implementation exists in two versions. The first option is offered by MSDN at this link, and the second (to support inheritance) here at this . The second implementation looks like this:public class Base: IDisposable
{
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
//вызов Dispose для managed переменных
}
//освобождение unmanaged ресурсов
}
~Base()
{
Dispose (false);
}
}
//потомок
public class Derived: Base
{
protected override void Dispose(bool disposing)
{
if (disposing)
{
//вызов Dispose для managed переменных
}
//освобождение unmanaged ресурсов
//вызов Dispose(bool) базового класса
base.Dispose(disposing);
}
//в потомках не перекрывайте метод Dispose без параметров
//и деструктор - они уже описаны в родительском классе.
}
* This source code was highlighted with Source Code Highlighter.The difference, as it is easy to see, is only that the Dispose (bool) method has changed from a closed to a protected and virtual one. Let's try to figure out why it is so difficult? And why do you need to know this? First, I will answer the second question: the proposed option for freeing resources must be known if you inherit from standard classes that have already implemented the IDisposable interface. An example is System.Windows.Forms.UserControl, which already has a Dispose method implemented.
A simple case: you spawned your control from UserControl. And your control owns an unmanaged resource. For example, he knows how to display an image using OpenGL, and for this he needs to control the rendering context. And not only manage, but also correctly release it. In this case, by overriding the Dispose method on the parent class, you interfere with the resource deallocation scheme, which is obviously bad.
In this case, it is possible to solve the problem by interacting with the resource release mechanism that MS proposed. And their solution looks quite simple:
1) There is a method for manual cleaning “right now and immediately”: Dispose ()
2) There is a method for cleaning “it's time to remove garbage” (called from GC): destructor
3) Both methods must perform the cleanup, which is carried out in a separate method: Dispose (bool)
The Dispose (bool) method, which performs the cleanup, receives an input that allows you to distinguish the cleanup from “right now and immediately” from “it's time clean up the trash. " What for? And because your class can own managed member variables that own unmanaged resources. And so that they can free unmanaged resources, they also need to call Dispose. In contrast to the situation when GC is engaged in cleaning. In this case, there were no demands to release resources “right now and immediately” and you can rely on GC.
In the implementation proposed for the case of inheritance from MSDN, there is one more peculiarity: there is no disposed flag variable. Those. no protection against multiple Dispose calls. Which is not very pretty. There is a slightly different implementation suggested in this article:
using System;
namespace RSDN
{
public abstract class DisposableType: IDisposable
{
bool disposed = false;
~DisposableType()
{
if (!disposed)
{
disposed = true;
Dispose(false);
}
}
public void Dispose()
{
if (!disposed)
{
disposed = true;
Dispose(true);
GC.SuppressFinalize(this);
}
}
public void Close()
{
Dispose();
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// managed objects
}
// unmanaged objects and resources
}
}
}
* This source code was highlighted with Source Code Highlighter.