Есть ли ситуация, в которой Dispose не будет вызываться для блока "using"?
Это был вопрос телефонного интервью, который у меня был: есть ли время, когда Dispose не будет вызываться на объект, область действия которого объявлена блоком using?
мой ответ был нет - даже если исключение произойдет во время использования блока, Dispose все равно будет вызван.
интервьюер не согласился и сказал, что если using
завернут в try
-catch
блок затем Dispose не будет вызван к моменту ввода блока catch.
Это идет вразрез с моим понимание конструкции, и я не смог найти ничего, что подтверждает точку зрения интервьюеров. Он прав или я неправильно понял вопрос?
8 ответов:
void Main() { try { using(var d = new MyDisposable()) { throw new Exception("Hello"); } } catch { "Exception caught.".Dump(); } } class MyDisposable : IDisposable { public void Dispose() { "Disposed".Dump(); } }
Это :
Disposed Exception caught
четыре вещи, которые заставят Dispose не вызываться в блоке using:
- сбой питания на вашей машине, когда внутри блока using.
- ваша машина расплавляется атомной бомбой, находясь внутри блока использования.
- исключения неуловимым как
StackOverflowException
,AccessViolationException
и возможно, другие.- окружающая среда.Содержащие
странно я читал об обстоятельствах, когда Dispose не будет вызван в блок использования только сегодня утром. Проверьте это блог на MSDN. Это связано с использованием Dispose с IEnumerable и ключевым словом yield, когда вы не повторяете всю коллекцию.
к сожалению, это не касается случая исключения, честно говоря, я не уверен в этом. Я бы ожидал, что это будет сделано, но, возможно, стоит проверить с помощью быстрого кода?
другие ответы о сбое питания,
Environment.FailFast()
итераторы или обмана со стороныusing
что этоnull
- все интересные. Но мне любопытно, что никто не упомянул, что я думаю, что это самая распространенная ситуация, когдаDispose()
не будет называться даже в присутствииusing
: когда выражение внутриusing
выдает исключение.конечно, это логично: выражение
using
бросил исключение, так что назначение не состоялось и есть ничего, что мы могли бы назватьDispose()
on. Но одноразовый объект уже может существовать,хотя он может быть наполовину инициализирован. И даже в этом состоянии он уже может содержать некоторые неуправляемые ресурсы. Это еще одна причина, почему важно правильно реализовать одноразовый шаблон.пример проблемного кода:
using (var f = new Foo()) { // something } … class Foo : IDisposable { UnmanagedResource m_resource; public Foo() { // obtain m_resource throw new Exception(); } public void Dispose() { // release m_resource } }
здесь, похоже
Foo
выпускаетm_resource
правильно, и мы используемusing
правильно тоже. Но этоDispose()
onFoo
is никогда не звонил, из-за исключения. Исправление в этом случае заключается в использовании финализатора и освобождении ресурса там тоже.
The
using
блок превращается компилятором вtry
/finally
блок сам по себе,внутри существующейtry
заблокировать.например:
try { using (MemoryStream ms = new MemoryStream()) throw new Exception(); } catch (Exception) { throw; }
становится
.try { IL_0000: newobj instance void [mscorlib]System.IO.MemoryStream::.ctor() IL_0005: stloc.0 .try { IL_0006: newobj instance void [mscorlib]System.Exception::.ctor() IL_000b: throw } // end .try finally { IL_000c: ldloc.0 IL_000d: brfalse.s IL_0015 IL_000f: ldloc.0 IL_0010: callvirt instance void [mscorlib]System.IDisposable::Dispose() IL_0015: endfinally } // end handler } // end .try catch [mscorlib]System.Exception { IL_0016: pop IL_0017: rethrow } // end handler
компилятор не будет переставлять вещи. Так и происходит вот так:
- исключение выбрасывается или распространяется на
using
блокаtry
часть- управление оставляет
using
блокаtry
часть, и входит егоfinally
часть
Да есть случай, когда dispose не будет вызван... вы слишком много думаете об этом. Случай, когда переменная в использующем блоке
null
class foo { public static IDisposable factory() { return null; } } using (var disp = foo.factory()) { //do some stuff }
не будет выдавать исключение, но если бы dispose был вызван в каждом случае. Однако конкретный случай, о котором упоминал ваш интервьюер, ошибочен.
интервьюер частично прав.
Dispose
не может правильно очистить базовый объект на индивидуальной основе.вот статья из MSDN о том, как избегайте проблем с использованием блока С WCF. Вот это официальный обходной путь Microsoft, хотя я сейчас думаю, что a сочетание этого ответа и этот самый элегантный подход.