Что произойдет, если я вернусь до окончания использования оператора? Будет ли вызвана диспозиция?

115

У меня есть следующий код

using(MemoryStream ms = new MemoryStream())
{
     //code
     return 0;
}

dispose()Метод вызывается в конце usingзаявления брекет }правильно? Так как I returnдо конца usingоператора, будет ли MemoryStreamобъект размещен должным образом? Что здесь происходит?

NLV
источник
4
@JonH: Найдите точную копию, затем проголосуйте за закрытие в этом случае, пожалуйста.
Noldorin,
@Noldorin: Я пошел искать обман по этому поводу, потому что я подумал, что его, должно быть, спрашивали раньше, но я не смог его найти. Думаю, есть еще простые вопросы. :)
Randolpho
@JonH и @Noldorin - дубликаты были бы представлены, когда вопрос был сформирован, он ищет «похожие вопросы», функция, которую люди, кажется, используют недостаточно.
Адам Хоулдсворт,
@ Адам: попробуйте сами. Скопируйте / вставьте заголовок и посмотрите, какие дубликаты будут представлены системой. Я вам подскажу: ответ отрицательный. То же самое, если вы ищете через поиск Google или SO. Похоже, этот вопрос раньше не задавали.
Randolpho
Ааа ... беру это обратно. Я только что нашел почти дубликат после очень тщательного поиска: stackoverflow.com/questions/2641692/… Теперь вопрос задается совершенно по-другому, но конечный вопрос почти такой же. Полагаю, мы все-таки можем считать это обманом.
Randolpho

Ответы:

167

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

Как правильно указывает @Noldorin, использование usingблока в коде компилируется в try/ finallyс вызовом Disposeв finallyблоке. Например, следующий код:

using(MemoryStream ms = new MemoryStream())
{
     //code
     return 0;
}

фактически становится:

MemoryStream ms = new MemoryStream();
try
{
    // code
    return 0;
}
finally
{
    ms.Dispose();
}

Таким образом, поскольку finallyон гарантированно выполняется после tryзавершения выполнения блока, независимо от его пути выполнения, Disposeон гарантированно будет вызван, несмотря ни на что.

Дополнительные сведения см. В этой статье MSDN .

Дополнение:
небольшая оговорка, которую следует добавить: поскольку Disposeвызывается гарантированно, почти всегда полезно убедиться, что Disposeпри реализации никогда не будет генерироваться исключение IDisposable. К сожалению, есть некоторые классы в библиотеке ядра , которые делают бросок в определенных обстоятельствах , когда Disposeназываются - я смотрю на вас, WCF Service Reference / Client Proxy! - и когда это происходит, может быть очень сложно отследить исходное исключение, если оно Disposeбыло вызвано во время раскрутки стека исключений, поскольку исходное исключение поглощается в пользу нового исключения, сгенерированного Disposeвызовом. Это может быть безумно неприятным. Или это ужасно сводит с ума? Один из двух. Возможно оба.

Randolpho
источник
4
Я думаю, вы обнаружите, что он эффективно скомпилирован в блок try-finally с вызовом Disposein finally, так что он эффективно отрабатывает реализацию finally, как вы описываете.
Noldorin
@Noldorin: точно. Хотя, полагаю, я мог бы сказать об этом прямо. Редактировать в ближайшее время ....
Randolpho
1
Также обратите внимание, что есть некоторые обстоятельства, при которых выполнение блока finally не гарантируется, например при использовании Environment.FailFast и возникновении исключения StackOverFlowException.
Christopher McAtackney
@ C.McAtackney: тоже хороший момент. Кроме того, IIRC, OutOfMemoryException; в основном, если вы не можете поймать исключение из-за критического сбоя выполнения, Dispose не будет вызываться. Конечно, в таком случае программа гарантированно выйдет из строя вместе с любой выделенной ей памятью, поэтому в 99,9% случаев это не проблема, если только вы не делаете нестандартные вещи, такие как запись в файл в своем методе удаления. , Если не считать катастрофического сбоя программы.
Randolpho
Вы никогда не должны использовать оператор using () с WCF - обратитесь к этой статье для получения дополнительной информации. Вот фрагмент, который я использую для прокси WCF: 'WCFProxy variableName = null; попробуйте {variableName = new WCFProxy (); // Код TODO здесь variableName.Proxy.Close (); variableName.Dispose (); } catch (исключение) {если (имя_переменной! = null && имя_переменной.Proxy! = null) {имя_переменной.Proxy.Abort (); } бросить; } '
Дэйв Блэк
18

usingоператоры ведут себя точно так же, как try ... finallyблоки, поэтому всегда будут выполняться на любом пути выхода кода. Однако я считаю, что они подвержены очень немногим и редким ситуациям, в которых finallyблоки не вызываются. Я могу вспомнить один пример: если поток переднего плана завершается, когда фоновые потоки активны: все потоки, кроме GC, приостановлены, то есть finallyблоки не запускаются.

Очевидное изменение: они ведут себя одинаково, за исключением логики, которая позволяет им обрабатывать IDisposable объекты, d'oh.

Бонусный контент: их можно складывать друг в друга (если типы различаются):

using (SqlConnection conn = new SqlConnection("string"))
using (SqlCommand comm = new SqlCommand("", conn))
{

}

А также через запятую (где типы совпадают):

using (SqlCommand comm = new SqlCommand("", conn), 
       SqlCommand comm2 = new SqlCommand("", conn))
{

}
Адам Хоулдсворт
источник
4

Ваш объект MemoryStream будет правильно удален, не о чем беспокоиться.

Отавио Десио
источник
0

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

Wil P
источник