Зачем нужно вызывать: this() в структуре использовать автоматические свойства в С#?

Если я определяю структуру в С# с использованием таких автоматических свойств, как это:

public struct Address
{
    public Address(string line1, string line2, string city, string state, string zip)
    {
        Line1 = line1;
        Line2 = line2;
        City = city;
        State = state;
        Zip = zip;
    }

    public string Line1 { get; protected set; }
    public string Line2 { get; protected set; }
    public string City { get; protected set; }
    public string State { get; protected set; }
    public string Zip { get; protected set; }
}

Когда я пытаюсь создать файл, я получаю ошибку компиляции, говоря The 'this' object cannot be used before all of its fields are assigned to. Это можно решить, изменив конструктор, чтобы сделать связанный вызов конструктора по умолчанию следующим образом:

public Address(string line1, string line2, string city, string state, string zip): this()
{
    Line1 = line1;
    Line2 = line2;
    City = city;
    State = state;
    Zip = zip;
}

Мой вопрос: почему это работает и что происходит? У меня есть предположение, и я попытался доказать это, посмотрев на Ил, но я только шучу, если я думаю, что могу сломать ИЛ. Но я предполагаю, что свойства auto работают, когда компилятор генерирует поля для ваших свойств за кулисами. Эти поля не могут быть доступны через код, все настройки и получение должны выполняться через свойства. При создании структуры конструктор по умолчанию не может быть явно определен. Поэтому за кулисами компилятор должен генерировать конструктор по умолчанию, который устанавливает значения полей, которые разработчик не может видеть.

Любые и все IL-мастера могут подтвердить или опровергнуть мою теорию.

Ответ 1

Примечание: с С# 6 это не требуется - но вы должны использовать автоматически реализованные свойства с С# 6 в любом случае...

this() гарантирует, что поля определенно назначены в отношении компилятора - он устанавливает все поля в значения по умолчанию. Перед тем, как начать доступ к любым свойствам, вы должны иметь полностью построенную структуру.

Это раздражает, но так оно и есть. Вы действительно хотите, чтобы это была структура? И зачем использовать защищенный сеттер в структуре (из которой не может быть получен)?

Ответ 2

Свойство - это не что иное, как инкапсуляция метода Get и/или метода Set. CLR имеет метаданные, которые указывают, что конкретные методы следует рассматривать как свойства, то есть компиляторы должны допускать некоторые конструкции, которые он не допускает с помощью методов. Например, если X является свойством read-write для Foo, компилятор переведет Foo.X += 5 в Foo.SET_X_METHOD(Foo.GET_X_METHOD() + 5) (хотя методы называются по-разному и обычно недоступны по имени).

Хотя autoproperty реализует пару методов get/set, которые обращаются к частному полю таким образом, чтобы вести себя более или менее как поле, с точки зрения любого кода вне свойства, автопроцессором является пара методов get/set, как и любое другое свойство. Следовательно, утверждение типа Foo.X = 5; переводится как Foo.SET_X_METHOD(5). Поскольку компилятор С# просто видит это как вызов метода, а так как методы не содержат метаданных для указания того, какие поля они читают или записывают, компилятор запрещает вызов метода, если не знает, что было записано все поле Foo.

Лично я советую избегать использования автопроцессоров со структурами. Autoproperties имеют смысл с классами, поскольку для свойств класса возможно поддерживать такие функции, как уведомления об обновлениях. Даже если ранние версии класса не поддерживают уведомления об обновлениях, если эти версии используют autoproperty, а не поле, это означает, что будущие версии могут добавлять функции уведомления об обновлениях, не требуя, чтобы потребители этого класса были переработаны. Однако структуры не могут существенно поддерживать большинство типов функций, которые можно было бы добавить к свойствам, подобным полям.

Кроме того, различия в производительности между полями и свойствами намного больше с большими структурами, чем с типами классов. Действительно, большая часть рекомендаций по предотвращению крупных структур является следствием этой разницы. Большие структуры могут быть очень эффективными, если вы избегаете их копирования без необходимости. Даже если бы у одной была огромная структура HexDecet<HexDecet<HexDecet<Integer>>>, где HexDecet<T> содержала открытые поля F0.. F15 типа T, утверждение вроде Foo = MyThing.F3.F6.F9; просто потребовало бы чтения одного целого из MyThing и сохранения до Foo, хотя MyThing будет иметь огромные структурные стандарты (4096 целых чисел, занимающих 16K). Кроме того, можно очень легко обновить этот элемент, например. MyThing.F3.F6.F9 += 26;. В отличие от этого, если F0.. F15 были авто-свойствами, оператор Foo = MyThing.F3.F6.F9 потребовал бы скопировать 1K данных из MyThing.F3 во временный (назовем его temp1, а затем 64 байта данных из temp1.F6 до temp2), прежде чем перейти к чтению 4 байтов данных из temp2.F9. Ик. Хуже того, попытка добавить 26 к значению в MyThing.F3.F6.F9 потребует нечто вроде var t1 = MyThing.F3; var t2 = t1.F6; t2.F9 += 26; t1.F6 = f2; MyThing.F3 = t1;.

Многие из давних жалоб на "изменяемые типы структур" - это действительно жалобы на типы структуры с свойствами чтения/записи. Просто замените свойства полями и проблемы исчезнут.

PS: Иногда бывает полезно иметь структуру, свойства которой получают доступ к объекту класса, к которому он содержит ссылку. Например, было бы неплохо иметь версию класса ArraySegment<T>, которая позволила бы сказать Var foo[] = new int[100]; Var MyArrSeg = New ArraySegment<int>(foo, 25, 25); MyArrSeg[6] += 9;, и последнее выражение добавит девять к элементу (25 + 6) из Foo. В более старых версиях С# это можно было сделать. К сожалению, частое использование автопроцессов в рамках Рамок, где поля были бы более уместными, приводило к широко распространенным жалобам на компилятор, позволяющим разработчикам недвижимости бесполезно называть структуры только для чтения; следовательно, вызов любого средства определения свойств в структуре, доступной только для чтения, теперь запрещен, независимо от того, изменит ли этот элемент свойств какие-либо поля структуры. Если бы люди просто воздерживались от того, чтобы создавать структуры, изменяемые с помощью установщиков свойств (делая поля доступными напрямую, когда пригодность была подходящей), компиляторам никогда не пришлось бы выполнять это ограничение.