Вопрос:
using (SomeClass sc = new SomeClass()){}
Что делает данная конструкция?
Ответ:
Выражение using (в данном случае корректно говорить о нем как о
выражении) позволяет использовать в своем блоке кода некоторый ресурс (в
данном случае это класс) с последующим неявным вызовом метода Dispose
интерфейса IDisposable.
Для класса
class A: IDisposable
{
public void Dispose()
{ }
}
Данное выражение преобразуется компилятором в следующую конструкцию:
A a = new A();
try
{ }
finally
{
if (a != null)
{
((IDisposable)a).Dispose();
}
}
Так как класс, это ссылочный тип данных – в блоке finally компилятор добавит
проверку на null. Это справедливо также и для nullable значимых типов.
Только тогда компилятор добавит проверку на наличия значения:
Nullable<A> a = default(A);
Try { }
finally
{
if (a.HasValue)
{
((IDisposable)a.GetValueOrDefault()).Dispose();
}
}
Если же наш тип является значимым типом, не поддерживающим null, то
никаких проверок не будет:
A a = default(A);
try
{ }
finally
{
((IDisposable)a).Dispose();
}
Коварный вопрос:
Если наш значимый тип приводится к интерфейсу IDisposable, то ведь он
должен быть упакован и его копия должна быть перемещена в кучу. А так как
мы вызываем Dispose у копии, то, соответственно, у оригинала Dispose вызван
не будет.
Ответ:
Не во всех случаях приведение к типу интерфейса приводит к упаковке
значения – компилятор может использовать constrained оптимизацию.
Подробнее опкод constrained описан тут:
https://docs.microsoft.com/enus/dotnet/api/system.reflection.emit.opcodes.constrained?view=netcore-3.1
Коварный вопрос:
Чем отличается
A a = new A();
using(a)
{ }
От
using(A a = new A())
{ }
Это одно и тоже?
Ответ:
Нет, это не одно и тоже. Во втором случае все будет так, как описано выше, в
первом же случае внутри самого выражения using вы будете работать с
переменной a, но Dispose будет вызван у копии данной переменной. Из этого
следует что первая запись некорректна.