Есть несколько способов обеспечить это. Основная помощь, которую я нахожу, заключается в использовании ключевого слова "using". Это применяется как таковое:
using(SqlConnection connection = new SqlConnection(myConnectionString))
{
/* utilise the connection here */
}
Это в основном означает:
SqlConnection connection = null;
try
{
connection = new SqlConnection(myConnectionString);
}
finally
{
if(connection != null) connection.Dispose();
}
Как таковой, он работает только с типами, которые реализуют IDisposable.
Это ключевое слово очень полезно при работе с объектами GDI, такими как ручки и кисти. Однако есть сценарии, в которых вы хотите удерживать ресурсы в течение более длительного периода времени, чем просто текущая область действия метода. Как правило, лучше избегать этого, если это возможно, но, например, при работе с SqlCe более эффективно поддерживать одно соединение с БД постоянно открытым. Поэтому никто не может избежать этой потребности.
В этом сценарии вы не можете использовать «использование», но вы все еще хотите иметь возможность легко восстановить ресурсы, удерживаемые соединением.
Есть два механизма, которые вы можете использовать, чтобы вернуть эти ресурсы.
Один через финализатор. Все управляемые объекты, находящиеся вне области видимости, в конечном итоге собираются сборщиком мусора. Если вы определили финализатор, GC вызовет его при сборе объекта.
public class MyClassThatHoldsResources
{
private Brush myBrush;
// this is a finaliser
~MyClassThatHoldsResources()
{
if(myBrush != null) myBrush.Dispose();
}
}
Однако приведенный выше код, к сожалению, дерьмо. Причина в том, что в момент завершения вы не можете гарантировать , какие управляемые объекты уже собраны, а какие нет. Следовательно, «myBrush» в приведенном выше примере, возможно, уже был удален сборщиком мусора. Поэтому не рекомендуется использовать финализатор для сбора управляемых объектов, он используется для очистки неуправляемых ресурсов.
Другая проблема с финализатором заключается в том, что он не является детерминированным. Скажем, например, у меня есть класс, который общается через последовательный порт. Только одно соединение с последовательным портом может быть открыто одновременно. Поэтому, если у меня есть следующий класс:
class MySerialPortAccessor
{
private SerialPort m_Port;
public MySerialPortAccessor(string port)
{
m_Port = new SerialPort(port);
m_Port.Open();
}
~MySerialPortAccessor()
{
if(m_Port != null) m_Port.Dispose();
}
}
Тогда, если бы я использовал объект следующим образом:
public static void Main()
{
Test1();
Test2();
}
private static void Test1()
{
MySerialPortAccessor port = new MySerialPortAccessor("COM1:");
// do stuff
}
private static void Test2()
{
MySerialPortAccessor port = new MySerialPortAccessor("COM1:");
// do stuff
}
У меня была бы проблема. Проблема в том, что финализатор не является детерминированным. То есть я не могу гарантировать, когда он запустится и, следовательно, приступить к утилизации моего объекта последовательного порта. Поэтому, когда я запускаю тест 2, я могу обнаружить, что порт все еще открыт.
Хотя я мог вызывать GC.Collect () между Test1 () и Test2 (), что решило бы эту проблему, не рекомендуется . Если вы хотите добиться максимальной производительности коллектора, позвольте ему делать свое дело.
Поэтому то, что я действительно хочу сделать, это:
class MySerialPortAccessor : IDispable
{
private SerialPort m_Port;
public MySerialPortAccessor(string port)
{
m_Port = new SerialPort(port);
m_Port.Open();
}
public void Dispose()
{
if(m_Port != null) m_Port.Dispose();
}
}
И я перепишу свой тест так:
public static void Main()
{
Test1();
Test2();
}
private static void Test1()
{
using( MySerialPortAccessor port = new MySerialPortAccessor("COM1:"))
{
// do stuff
}
}
private static void Test2()
{
using( MySerialPortAccessor port = new MySerialPortAccessor("COM1:"))
{
// do stuff
}
}
Теперь это будет работать.
Так что из финализатора? Зачем его использовать?
Неуправляемые ресурсы и возможные реализации, которые не вызывают Dispose.
Как создатель библиотеки компонентов, которую используют другие; их код может забыть избавиться от объекта. Возможно, что-то еще может убить процесс, и, следовательно, .Dispose () не произойдет. Из-за этих сценариев должен быть реализован финализатор для очистки любого неуправляемого ресурса как «наихудшего» сценария, но утилизация должна также привести в порядок эти ресурсы, чтобы у вас была процедура «детерминированной очистки».
Итак, в заключение, шаблон, рекомендованный в книге .NET Framework Guidelines , должен реализовать оба варианта следующим образом:
public void SomeResourceHoggingClass, IDisposable
{
~SomeResourceHoggingClass()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
}
// virtual so a sub class can override it and add its own stuff
//
protected virtual void Dispose(bool deterministicDispose)
{
// we can tidy managed objects
if(deterministicDispose)
{
someManagedObject.Parent.Dispose();
someManagedObject.Dispose();
}
DisposeUnmanagedResources();
// if we've been disposed by .Dispose()
// then we can tell the GC that it doesn't
// need to finalise this object (which saves it some time)
//
GC.SuppressFinalize(this);
}
}