Защо е необходимо да се извиква :this() на структура, за да се използват автоматични свойства в c#?

Ако дефинирам структура в C#, използвайки автоматични свойства като това:

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;
}

Въпросът ми е защо това работи и какво се случва? Имам предположение и се опитах да го докажа, като разгледах IL, но само се шегувам, ако мисля, че мога да разбия IL. Но моето предположение е, че автоматичните свойства работят, като компилаторът генерира полета за вашите свойства зад кулисите. Тези полета не могат да бъдат достъпни чрез код, всички настройки и получаване трябва да се извършват чрез свойствата. Когато създавате структура, конструкторът по подразбиране не може да бъде изрично дефиниран. Така че зад кулисите компилаторът трябва да генерира конструктор по подразбиране, който задава стойностите на полетата, които разработчикът не може да види.

Всеки магьосник на IL е добре дошъл да докаже или опровергае моята теория.


person NerdFury    schedule 07.11.2008    source източник
comment
Нов protected член не трябва да бъде разрешен в struct точно както не е разрешен в sealed class.   -  person Jeppe Stig Nielsen    schedule 11.11.2017


Отговори (2)


Забележка: от C# 6 това не е задължително - но така или иначе трябва да използвате автоматично внедрени свойства само за четене с C# 6...

this() се уверява, че полетата са определено присвоени, що се отнася до компилатора - той задава всички полета на техните стойности по подразбиране. Трябва да имате напълно изградена структура, преди да можете да започнете достъп до които и да било свойства.

Досадно е, но това е положението. Сигурни ли сте, че наистина искате това да е структура? И защо да използвате защитен сетер на структура (която не може да бъде извлечена от)?

person Jon Skeet    schedule 07.11.2008
comment
Защитеният комплект е навик. Не правя много структури. И това не е от действителния код, който пиша, написах го специално, за да илюстрирам смисъла на моя въпрос. Но адресите са неизменни и са добър избор за структури. Хората се местят на нови адреси, адресите не се променят. - person NerdFury; 07.11.2008
comment
Неизменен != struct. Не ми се струва много структурно. Това не е непременно лош избор, но не е такъв, който бих направил. Все пак бих направил неизменността по-ясна: използвайте нормално свойство (само за четене) и поле само за четене. Жалко е, че автоматичните свойства не могат да имат настройки само за четене (продължение) - person Jon Skeet; 07.11.2008
comment
... което би било достъпно само в конструктори, по същия начин като присвояването на поле само за четене. След това компилаторът може да генерира поле само за четене зад кулисите и да преобразува достъпа на сетер в достъп до поле директно. - person Jon Skeet; 07.11.2008
comment
Може да искате да редактирате, за да отбележите, че това вече не е необходимо от C# 6 нататък - person Marc Gravell; 15.07.2015
comment
@MarcGravell: Господи - дори не знаех за тази промяна. интересно Разбира се, в C# 6 бихте искали да използвате автоматично внедрени свойства само за четене... - person Jon Skeet; 15.07.2015
comment
@xdevel2000: Не трябва, ако присвоявате на всички автоматично внедрени свойства и други полета. Предлагам ви да се опитате да измислите кратък, но пълен пример. - person Jon Skeet; 10.02.2016
comment
Любопитно, защо C# компилаторът не вмъква имплицитно : this() в такива случаи? - person Vlad; 13.02.2017
comment
@Vlad: Е, това може да скрие грешка, която сте забравили да присвоите на конкретно поле (не свойство). Да не се изисква this() за автоматични подпори е по-добро решение IMO. - person Jon Skeet; 13.02.2017
comment
Използването на protected дори е грешка по време на компилиране за членове на struct или sealed class (или static class), включително инструменти за достъп като set. - person Jeppe Stig Nielsen; 11.11.2017

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

Въпреки че автоматичното свойство имплементира чифт get/set методи, които имат достъп до частно поле по такъв начин, че да се държат повече или по-малко като поле, от гледна точка на всеки код извън свойството, автоматичното свойство е двойка get/ задайте методи точно като всяко друго свойство. Следователно изявление като Foo.X = 5; се превежда като Foo.SET_X_METHOD(5). Тъй като C# компилаторът просто вижда това като извикване на метод и тъй като методите не включват никакви метаданни, за да посочат какви полета четат или пишат, компилаторът ще забрани извикването на метода, освен ако не знае, че всяко поле на Foo е записано.

Лично моят съвет би бил да избягвате използването на автосвойства със структурни типове. Автоматичните свойства имат смисъл с класовете, тъй като е възможно свойствата на класа да поддържат функции като известия за актуализиране. Дори ако ранните версии на клас не поддържат известия за актуализация, ако тези версии използват автоматично свойство, а не поле, ще означава, че бъдещите версии могат да добавят функции за известяване за актуализация, без да се налага потребителите на класа да бъдат преработени. Структурите обаче не могат смислено да поддържат повечето от типовете функции, които може да искате да добавите към свойства, подобни на поле.

Освен това разликите в производителността между полетата и свойствата са много по-големи при големите структури, отколкото при типовете класове. Всъщност голяма част от препоръките да се избягват големи структури са следствие от тази разлика. Големите структури всъщност могат да бъдат много ефективни, ако човек избягва ненужното им копиране. Дори ако някой има огромна структура 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. В по-старите версии на C# можеше да се направи това. За съжаление, честото използване на автоматични свойства в рамката, където полетата биха били по-подходящи, доведе до широко разпространени оплаквания относно компилатора, който позволява безполезно извикване на настройки на свойства в структури само за четене; следователно извикването на който и да е инструмент за настройка на свойства на структура само за четене вече е забранено, независимо дали инструментът за настройка на свойства действително би променил полета на структурата. Ако хората просто се бяха въздържали да правят структури променливи чрез настройки на свойства (правейки полета директно достъпни, когато променливостта беше подходяща), компилаторите никога не би трябвало да прилагат това ограничение.

person supercat    schedule 01.10.2012