Для типов значений переменная data
копируется в другое безымянное временное в начале оператора using.В соответствии со спецификацией, эта копия будет вести себя так, как если бы она была в штучной упаковке для IDisposable и Dispose, вызываемого (обратите внимание, однако, что компилятор C # на самом деле не указывает значение, подробнее об этом в конце статьи).Это задокументировано в спецификации C # :
Использование оператора вида
using (ResourceType resource = expression) statement
соответствует одному из трех возможных расширений.Когда ResourceType является необнуляемым типом значения, расширение будет
{
ResourceType resource = expression;
try {
statement;
}
finally {
((IDisposable)resource).Dispose();
}
}
Обратите внимание, что ваш оператор using не является декларацией, а просто выражением.Спецификация также охватывает это:
Оператор использования вида
using (expression) statement
имеет те же три возможных расширения.В этом случае ResourceType неявно является типом выражения времени компиляции, если оно есть.В противном случае сам интерфейс IDisposable используется как ResourceType.Переменная ресурса недоступна и невидима для встроенного оператора.
Таким образом, ваша модификация data
не видна внутри Dispose, потому что копия уже была сделана.Относительно недавние версии компилятора C # (поставляются с VS 2019) будут выдавать предупреждение для этого случая.
Является ли значение на самом деле в штучной упаковке?
Нет.Несмотря на появление приведенных в спецификации и даже некоторых декомпиляций на C #.Компилятор разрешен и фактически не упаковывает значение.Статья Эрика Липперта (ссылка также в комментариях) содержит некоторые дополнительные детали по этому вопросу.Чтобы увидеть, что на самом деле происходит, давайте посмотрим на IL в finally:
IL_0023: ldloca.s 1
IL_0025: constrained. MyStruct
IL_002b: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0030: endfinally
Сначала неназванное временное устройство загружается обратно в стек оценки.Это неизмененная копия, упомянутая ранее.Затем волшебство происходит через ограниченный код операции .Это специальная инструкция, которая информирует JIT о том, что вызов выполняется непосредственно для типа, и если это тип значения, который реализует метод, то нет необходимости устанавливать флажок для виртуального вызова через интерфейс.
В статье Эрика упоминается обновление спецификации C #, уточняющее правила бокса, что, вероятно, является следующим моментом:
Реализация может реализовывать заданный оператор использования по-разному, напримерпо соображениям производительности, если поведение соответствует приведенному выше расширению.