Как стать автором
Обновить

Комментарии 28

если поискать по тэгу IDisposable на хабре, то можно найти это.
В целом, управление ресурсами в .net-е — это конкретный баян, о котором знает практически каждый .net девелопер. Но вот, если спросить, что такое Dispose паттерн, то многие почему-то тупо копи-пастят этот паттерн из их любимой книги, нисколько не думая, что этот паттерн должен решать и почему они добавили пустой финализатор и для чего вообще нужен второй метод Dispose.

Здесь же я не столько показываю сам паттерн (без этого никак), сколько акцентирую внимание на его излишнюю тяжеловесность, которая в 99.999% просто не нужна.

З.Ы. Это ответ на оба предыдущих коммента. Ссылка на кывт — вообще не в тему, там про GC и совсем мало про IDisposable, а в ссылке на хабре нет того, ради чего эта статья писалась: критического взгляда на Dispose паттерн.
Совершенно согласен, сколько раз натыкался в коде приложения на реализацию полновесного Disposable в классах где нет и не будет никогда неуправляемых ресурсов. На вопрос «а почему нельзя было реализовать один метод Dispose?» разработчик обычно начинает бубнить, что мол шаблон ведь такой.
В жирном тесте пропали пробелы.

В процитированном коде что-то не так :-)
// Никаких параметров, этот метод должен освобождать только неуправляемые ресурсы
protected virtual void DisposeManagedResources() {}

Касательно (***). Так не просто такой же синтаксис, они и называются деструкторами, хотя делают не тоже самое.

В примечении (****) нет ссылки на материал о CER.
Отличная статья, спасибо.
Спасибо за статью.

Скажите, а я получается, дурак, если пишу вот так:

f = File.Open("filename.dat", File.Open, FileAccess.Read, FileShare.Read);
...
f.Close();


вместо using?
Я что-то не освобождаю?
Просто, если в "..." вы получите исключение (либо явно в вашем коде, либо в функции, которую вы там вызываете), то файл закрыт не будет.

Поэтому, да, вы «можете» что-то не освободить.
Корректный код без using такой:

File f;
try
{
f = FIle.Open("filename.dat", File.Open, FileAccess.Read, FileShare.Read);
...
}
finally
{
f.Close();
}
А, ну это понятно. Я обычно что-то вроде этого и пишу. Чем-то мне using не нравится. Сам не знаю, чем :D
А если исключение вылетит в методе File.Open? Надо делать проверку на null.
Да, конечно.
Именно поэтому лучше использовать using, вместо рукопашного использования try/finally.

Кроме того, при использовании using будет сложнее использовать переменную после вызова Dispose/Close:
FileStream fs = null;
try
{
fs = File.OpenRead("");
}
finally
{
// Да, не забываем о проверке на null
if (fs != null)
fs.Close();
}

// переменная fs доступна после закрытия файла!
var b = fs.ReadByte();

using (var fs2 = File.OpenRead(""))
{

}
// переменная fs2 недоступна вне блока using
С другой стороны это плюс, т.к. переменная теперь «не мешает», да и толку от нее, если был вызван Dispose. Кстати говоря, как я помню, такое должно сработать:
var a = File.Open("a");
using (a)
{

}
a.ToString(); // Должно быть в области видимости


Хотя зачем я это все рассказываю, вы наверное лучше меня знаете :)
Да, конечно. Просто в этом случае нужно сделать дополнительные усилия, чтобы отпилить себе ногу, а с try/finally это можно сделать по-умолчанию.
А в этом случае будет автоматически вызван метод a.Dispose() по выходу из using?
А вызов a.ToString(); приведёт к исключению?
Никто не вызывает File.Open без catch. Тогда какой смысл в using?
Разница в том, что на том уровне, на котором файл открывают, обычно не просто его открывают в блоке try/catch, его открывают в try/finally, чтобы закрыть его успешно в случае успешной и неуспешной работы.

При этом кэтчить исключение на том уровне, где происходит работа с файлом обычно не имеет смысла, поскольку восстановиться от этой проблемы все равно на этом уровне нельзя.
Кстати, поэтому получение ресурса нужно делать до try. Если исключение произойдёт в этот момент, то управление не попадёт в блок try. А если попали — значит, ресурс получен.
По моему все это уже было хорошо разжеванно в книге Джефри Рихтера «Программирование на платформе .NET Framework».
Я не читал этой книги, а статья была очень интересной.
У Рихтера правда этот паттерн описан отлично, но у него есть одна проблема: Джеффри достаточно категоричен в описании паттерна и очень авторитетен, и именно из-за него многие начали использовать паттерн там, где нужно и там, где нет.
2. Класс содержит метод Dispose(bool disposing), который и делает всю работу по освобождению ресурсов; параметр disposing говорит о том, вызывается ли этот метод из метода Dispose или из финализатора.


3. Метод Dispose всегда реализуется следующим образом: вначале вызывается метод Dispose(true), а затем может следовать вызов метода GC.SuppressFinalize(), который предотвращает вызов финализатора.

Один я вижу здесь противоречие? Зачем, запрещая вызов финализатора, писать логику по определению, откуда вызван метод Dispose?
И соответственно, основываясь на моем предыдущем вопросе, в чем профит от этого:
4. Метод Dispose(booldisposing) содержит две части: (1) если этот метод вызван из метода Dispose (т.е. параметр disposing равен true), то мы освобождаем управляемые и неуправляемые ресурсы и (2) если метод вызван из финализатора во время сборки мусора (параметр disposing равен false), то мы освобождаем только неуправляемые ресурсы.

?

зы. Мне правда интересно :)
«Новость «не очень» заключается в том, что работа с ресурсами даже сложнее, чем здесь описано (*****)»

А куда ведет эта сноска?
В подвал статьи:
(****) Здесь я, например, не говорил о том, как можно получить «утечку ресурсов» при появлении исключений или о проблемах с изменяемыми значимыми типами, реализующими интерфейс IDisposable. Об этом я уже писал ранее в заметках «Гарантии безопасности исключений» и «О вреде изменяемых значимых типов» соответственно.
У автора съехал сквозной счётчик звёздочек — у него 2 раза по 3 звезды.
Правильно ли я понимаю, что если внутри класса используется SafeHandle, то закрывать его можно даже если Dispose вызван из деструктора?

p.s.
Можно или даже нужно?

p.p.s
Приведите пример неуправляемого ресурса (непосредственное владение неуправляемым ресурсом в классе), пожалуйста.
Вижу что поздно, но вдруг получится обсуждение.

Правильно ли я понимаю, что если внутри класса используется SafeHandle, то закрывать его можно даже если Dispose вызван из деструктора?

Да, но если только этот класс не наследуется от CriticalFinalizerObject. Иначе порядок вызовов снова становится неочевиден.

p.s.
Можно или даже нужно?

Бывает что можно, но уж точно не нужно. Финализатор уже есть у SafeHandle. А сам SafeHandle является управляемым ресурсом и как гласит паттерн, освобождать его нужно только при прямом вызове. А на самом деле лучше воспользовать посленим советом статьи и не мешать ресурсы. Тогда финализатор не нужен в-принципе.

p.p.s
Приведите пример неуправляемого ресурса (непосредственное владение неуправляемым ресурсом в классе), пожалуйста.

Да чуть ли не любой кусок кода, где рядом IntPtr, т.е. там, где работа с ресурсом напрямую. Насколько помню Pdfium.NET SDK работает с pdfium.dll именно так.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории