Есть ли ситуация, в которой 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:

  1. сбой питания на вашей машине, когда внутри блока using.
  2. ваша машина расплавляется атомной бомбой, находясь внутри блока использования.
  3. исключения неуловимым как StackOverflowException, AccessViolationException и возможно, другие.
  4. окружающая среда.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

компилятор не будет переставлять вещи. Вот как это происходит:

  1. исключение выбрасывается или распространяется на using блока try часть
  2. управление оставляет using блока try часть, и входит finally часть

using (var d = new SomeDisposable()) {
    Environment.FailFast("no dispose");
}

Да есть случай, когда 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, хотя я теперь думаю, что сочетание этого ответа и этот самый элегантный подход.