Ответ 1
То, что вы заметили, является фундаментальной проблемой при проектировании Dispose
и using
, для которых пока нет хорошего решения. IMHO лучшим вариантом было бы иметь версию Dispose
, которая получает в качестве аргумента любое исключение, которое может быть отложено (или null
, если ни один не находится в ожидании), и может либо регистрировать, либо инкапсулировать это исключение, если ему нужно выбросить один из своих. В противном случае, если у вас есть контроль над кодом, который может вызвать исключение в using
, а также в Dispose
, вы можете использовать какой-то внешний канал данных, чтобы Dispose
знал о внутреннее исключение, но это скорее hokey.
Слишком плохо, что нет надлежащей поддержки языка для кода, связанного с блоком finally
(явно или неявно через using
), чтобы узнать, правильно ли выполняется связанный try
, а если нет, что пошло не так. Понятие, что Dispose
должно терпеть неудачу, ИМХО очень опасно и неправильно. Если объект инкапсулирует файл, открытый для записи, и Dispose
закрывает файл (общий шаблон), и данные не могут быть записаны, при условии, что возврат вызова Dispose
обычно приведет к тому, что вызывающий код верят, что данные были написаны правильно, потенциально позволяя ему перезаписать единственную хорошую резервную копию. Кроме того, если файлы должны быть явно закрыты и вызов Dispose
без закрытия файла должен считаться ошибкой, это означает, что Dispose
должен вызывать исключение, если защищенный блок в противном случае завершил бы нормально, но если защищенный блок не вызвал Close
, потому что сначала возникло исключение, причем Dispose
throw исключение было бы очень бесполезным.
Если производительность не является критичной, вы можете написать метод оболочки в VB.NET, который принял бы два делегата (из типов Action
и Action<Exception>
), вызовет первый в блоке try
, а затем вызовите второй в блоке finally
с исключением, произошедшим в блоке try
(если есть). Если метод-оболочка был написан на VB.NET, он мог бы обнаружить и сообщить об исключении, которое не произошло, чтобы не поймать и не восстановить его. Другие шаблоны были бы возможны. Большинство применений обертки будут связаны с закрытием, которые являются icky, но оболочка может по крайней мере достичь правильной семантики.
Альтернативная конструкция обертки, которая позволит избежать замыканий, но потребует, чтобы клиенты правильно ее использовали и обеспечили бы небольшую защиту от неправильного использования, использовали бы тестовое использование, например:
var dispRes = new DisposeResult();
...
try
{
.. the following could be in some nested routine which took dispRes as a parameter
using (dispWrap = new DisposeWrap(dispRes, ... other disposable resources)
{
...
}
}
catch (...)
{
}
finally
{
}
if (dispRes.Exception != null)
... handle cleanup failures here
Проблема с этим подходом заключается в том, что нет возможности гарантировать, что кто-либо когда-либо оценит dispRes.Exception
. Можно использовать финализатор для регистрации случаев, когда dispRes
получает отказ, даже если его не проверяли, но не было возможности различать случаи, когда это произошло, потому что исключение выкидывало код вне теста if
или потому, что программист просто забыли проверить.
PS - Другой случай, когда Dispose
действительно должен знать, происходят ли исключения, когда объекты IDisposable
используются для переноса блокировок или других областей, где инварианты объектов могут временно быть недействительными, но ожидается, что они будут восстановлены до того, как код покинет объем. Если возникает исключение, код часто не должен рассчитывать на устранение исключения, но должен тем не менее принимать меры на его основе, оставляя блокировку не сохраненной и не выпущенной, а скорее недействительной, так что любая настоящая или будущая попытка ее приобретения выдает исключение, Если в будущем нет попыток получить блокировку или другой ресурс, тот факт, что он недействителен, не должен нарушать работу системы. Если ресурс критически необходим для какой-либо части программы, ее недействительность приведет к тому, что эта часть программы умрет при минимизации ущерба, который она наносит другому. Единственный способ, которым я знаю, действительно реализовать этот случай с хорошей семантикой, - это использовать нечеткие замыкания. В противном случае единственная альтернатива - требовать явных недействительных/проверенных вызовов и надеяться, что любые операторы возврата внутри части кода, где ресурс недействителен, предшествуют вызовам для проверки.