ОБНОВЛЕНИЕ : я использовал этот вопрос как основу для статьи, которую можно найти здесь ; см. это для дополнительного обсуждения этого вопроса. Спасибо за хороший вопрос!
Хотя ответ Шабсе, конечно, правильный и отвечает на заданный вопрос, есть важный вариант вашего вопроса, который вы не задавали:
Что произойдет, если font4 = new Font()
выбрасывается после того, как неуправляемый ресурс был выделен конструктором, но до того, как ctor вернется и заполнится font4
ссылкой?
Позвольте мне прояснить это немного подробнее. Предположим, у нас есть:
public sealed class Foo : IDisposable
{
private int handle = 0;
private bool disposed = false;
public Foo()
{
Blah1();
int x = AllocateResource();
Blah2();
this.handle = x;
Blah3();
}
~Foo()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!this.disposed)
{
if (this.handle != 0)
DeallocateResource(this.handle);
this.handle = 0;
this.disposed = true;
}
}
}
Теперь у нас есть
using(Foo foo = new Foo())
Whatever(foo);
Это то же самое, что и
{
Foo foo = new Foo();
try
{
Whatever(foo);
}
finally
{
IDisposable d = foo as IDisposable;
if (d != null)
d.Dispose();
}
}
ХОРОШО. Допустим Whatever
кидает. Затем finally
блок запускается, и ресурс освобождается. Нет проблем.
Допустим Blah1()
кидает. Затем бросок происходит до выделения ресурса. Объект был выделен, но ctor никогда не возвращается, поэтому foo
никогда не заполняется. Мы никогда try
не вводили объект, поэтому мы никогда не вводим его finally
. Ссылка на объект потеряна. В конце концов сборщик мусора обнаружит это и поместит в очередь финализатора. handle
все еще равно нулю, поэтому финализатор ничего не делает. Обратите внимание на то, что финализатор должен быть устойчивым перед лицом финализируемого объекта, конструктор которого никогда не завершился . Вы необходимы написать настолько сильные финализаторы. Это еще одна причина, по которой вам следует доверить написание финализаторов экспертам, а не пытаться делать это самостоятельно.
Допустим Blah3()
кидает. Бросок происходит после выделения ресурса. Но опять же, foo
никогда не заполняется, мы никогда не входим вfinally
, и объект очищается потоком финализатора. На этот раз дескриптор не равен нулю, и финализатор очищает его. Опять же, финализатор работает с объектом, конструктор которого никогда не преуспел, но финализатор все равно запускается. Очевидно, так и должно быть, потому что на этот раз надо было поработать.
А теперь допустим Blah2()
кидает. Бросок происходит после того, как ресурс выделен, но до handle
заполнения! Опять же, финализатор запустится, но сейчас handle
он все еще равен нулю, и мы пропускаем дескриптор!
Вам нужно написать чрезвычайно умный код, чтобы предотвратить эту утечку. Теперь, в случае с вашим Font
ресурсом, кого это волнует? У нас утечка дескриптора шрифта, большое дело. Но если вы абсолютно решительно требуете, чтобы каждый неуправляемый ресурс был очищен, независимо от времени возникновения исключений, то перед вами стоит очень сложная проблема.
CLR должна решить эту проблему с помощью блокировок. Начиная с C # 4, блокировки, использующие этот lock
оператор, были реализованы следующим образом:
bool lockEntered = false;
object lockObject = whatever;
try
{
Monitor.Enter(lockObject, ref lockEntered);
lock body here
}
finally
{
if (lockEntered) Monitor.Exit(lockObject);
}
Enter
был очень тщательно написан, так что независимо от того, какие исключения выбрасываются , lockEntered
устанавливается значение true тогда и только тогда, когда блокировка действительно была взята. Если у вас похожие требования, то вам нужно написать:
public Foo()
{
Blah1();
AllocateResource(ref handle);
Blah2();
Blah3();
}
и напишите так AllocateResource
умно, Monitor.Enter
чтобы, что бы ни происходило внутри AllocateResource
, handle
заполнялось тогда и только тогда, когда его нужно освободить.
Описание методов для этого выходит за рамки этого ответа. Проконсультируйтесь со специалистом, если у вас есть это требование.