Есть ли ситуация, когда Dispose не будет вызываться для блока using? - PullRequest
66 голосов
/ 29 сентября 2011

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

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

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

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

Ответы [ 8 ]

58 голосов
/ 29 сентября 2011

Четыре вещи, которые приведут к тому, что Dispose не будет вызываться в блоке using:

  1. Сбой питания на вашей машине при использовании блока.
  2. Ваша машина плавится от атомной бомбы внутри блока использования.
  3. Неуловимые исключения , такие как StackOverflowException, AccessViolationException и , возможно, другие .
  4. Environment.FailFast
53 голосов
/ 29 сентября 2011
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

Так что я согласен с вами, а не с умным интервьюером ...

25 голосов
/ 29 сентября 2011

Как ни странно, я читал об обстоятельствах, когда Dispose не будет вызван в блоке использования только сегодня утром. Оформить заказ блог на MSDN. Все дело в использовании Dispose с IEnumerable и ключевого слова yield, когда вы не выполняете итерацию всей коллекции.

К сожалению, это не касается случая исключения, честно говоря, я не уверен в этом. Я бы ожидал, что это будет сделано, но, может быть, стоит проверить с небольшим количеством кода?

21 голосов
/ 30 сентября 2011

Другие ответы о сбое питания, Environment.FailFast(), итераторах или мошенничестве с помощью using чего-то, что является null, все интересны.Но мне кажется любопытным, что никто не упомянул о том, что, на мой взгляд, является наиболее распространенной ситуацией, когда Dispose() не будет вызываться даже при наличии using: когда выражение внутри using выдает исключение.

Конечно, это логично: выражение в using вызвало исключение, поэтому присвоение не состоялось, и нет ничего, что мы могли бы назвать Dispose().Но одноразовый предмет уже может существовать, хотя он может быть в полуинициализированном состоянии.И даже в этом состоянии он уже может содержать некоторые неуправляемые ресурсы.Это еще одна причина, почему важно правильно реализовать одноразовый шаблон.

Пример проблемного кода:

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() на Foo никогда не вызывается из-за исключения.Решением в этом случае является использование финализатора и освобождение ресурса там.

19 голосов
/ 29 сентября 2011

Блок 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. Исключение выдается или распространяется на try часть *1019* блока
  2. Элемент управления покидает часть try блока using и входит в его finally часть
  3. Объект располагается по коду в блоке finally
  4. Элемент управления покидает блок finally, а исключение распространяется на внешний try
  5. Control покидает внешний try и переходит в обработчик исключений

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

Единственный нормальный случай, когда этого не произойдет, - это генератор (извините, "итератор"). Итератор превращается в полусложный конечный автомат, и блоки finally не гарантированно будут работать, если он станет недоступным после yield return (но до его удаления).

13 голосов
/ 29 сентября 2011
using (var d = new SomeDisposable()) {
    Environment.FailFast("no dispose");
}
5 голосов
/ 29 сентября 2011

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

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

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

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

0 голосов
/ 19 ноября 2011

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

Например, WCF имеет несколько известных проблем, если в блоке using генерируется исключение. Ваш интервьюер, вероятно, думал об этом.

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

...