Есть ли ситуация, в которой Dispose не будет вызываться для блока "using"?


Это был вопрос телефонного интервью, который у меня был: есть ли время, когда Dispose не будет вызываться на объект, область действия которого объявлена блоком using?

мой ответ был нет - даже если исключение произойдет во время использования блока, Dispose все равно будет вызван.

интервьюер не согласился и сказал, что если using завернут в try -catch блок затем Dispose не будет вызван к моменту ввода блока catch.

Это идет вразрез с моим понимание конструкции, и я не смог найти ничего, что подтверждает точку зрения интервьюеров. Он прав или я неправильно понял вопрос?

8 66

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. окружающая среда.Содержащие

странно я читал об обстоятельствах, когда 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() on Foo 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

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

  1. исключение выбрасывается или распространяется на using блока try часть
  2. управление оставляет using блока try часть, и входит его finally часть
using (var d = new SomeDisposable()) {
    Environment.FailFast("no dispose");
}

Да есть случай, когда dispose не будет вызван... вы слишком много думаете об этом. Случай, когда переменная в использующем блоке null

class foo
{
    public static IDisposable factory()
    {
        return null;
    }
}

using (var disp = foo.factory())
{
    //do some stuff
}

не будет выдавать исключение, но если бы dispose был вызван в каждом случае. Однако конкретный случай, о котором упоминал ваш интервьюер, ошибочен.

интервьюер частично прав. Dispose не может правильно очистить базовый объект на индивидуальной основе.

вот статья из MSDN о том, как избегайте проблем с использованием блока С WCF. Вот это официальный обходной путь Microsoft, хотя я сейчас думаю, что a сочетание этого ответа и этот самый элегантный подход.