Есть ли ситуация, в которой 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
и возможно, другие. - окружающая среда.FailFast
странно, я читал об обстоятельствах, когда 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
правильно тоже. Но ... --4--> on Foo
is никогда не звонил, из-за исключения. Исправление в этом случае-использовать финализатор и освободить ресурс там тоже.
на 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 не будет вызван... вам больше думать. Случай, когда переменная в блоке using равна null
class foo
{
public static IDisposable factory()
{
return null;
}
}
using (var disp = foo.factory())
{
//do some stuff
}
не будет выдавать исключение, но будет, если dispose был вызван в каждом случае. Конкретный случай, который упомянул ваш интервьюер, ошибочен.
интервьюер частично прав. Dispose
может неправильно очистить базовый объект в каждом конкретном случае.
WCF, например, имеет несколько известных проблем, если исключение создается в блоке using. Ваш интервьюер, вероятно, думал об этом.
вот статья из MSDN о том, как избегайте проблем с использованием блока С WCF. Вот официальный обходной путь Microsoft, хотя я теперь думаю, что сочетание этого ответа и этот самый элегантный подход.