Почему поле структуры сбрасывается после оператора using? - PullRequest
1 голос
/ 18 апреля 2019

Я заметил странное поведение с IDisposable структурами.Кажется, что метод dispose вызывается в новом экземпляре с полями, установленными в значения по умолчанию.

public static class Example
{
    public static void Main()
    {
        var data = new MyStruct();

        using (data)
        {
            data.Foo = "some string";
            Console.WriteLine(data.Foo); //some string
        }

        Console.WriteLine(data.Foo); //some string
    }

}

public struct MyStruct : IDisposable
{
    public string Foo;


    public void Dispose()
    {
        Console.WriteLine(Foo);//null!
        Foo = "some string";
    }
}

Я предполагаю, что это происходит, потому что объект приведен к IDisposable в блоке finally, и так как у меня естьтип значения здесь, новый экземпляр создается.Я не понимаю, почему поле не копируется в новый экземпляр?Когда я упаковываю структуру, поля копируются:

    var s = new MyStruct();
    s.Foo = "1";

    var s2 = (MyStruct)(object)s;
    Console.WriteLine(s.Foo);//1
    Console.WriteLine(s2.Foo);//1

1 Ответ

4 голосов
/ 18 апреля 2019

Для типов значений переменная 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 #, уточняющее правила бокса, что, вероятно, является следующим моментом:

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

...