Автоматические свойства и структуры не смешиваются?

Обходя некоторые небольшие структуры, отвечая на этот пост, я неожиданно обнаружил следующее:

Следующая структура с использованием поля int совершенно допустима:

struct MyStruct
{ 
    public MyStruct ( int size ) 
    { 
        this.Size = size; // <-- Legal assignment.
    } 

    public int Size; 
}

Однако следующая структура, использующая автоматическое свойство, не компилируется:

struct MyStruct
{ 
    public MyStruct ( int size ) 
    { 
        this.Size = size; // <-- Compile-Time Error!
    } 

    public int Size{get; set;}
}

Возвращается ошибка: «Объект 'this' нельзя использовать до тех пор, пока ему не будут присвоены все его поля». Я знаю, что это стандартная процедура для структуры: резервное поле для любого свойства должно быть назначено напрямую (а не через метод доступа set свойства) из конструктора структуры.

Решение - использовать явное резервное поле:

struct MyStruct
{ 
    public MyStruct(int size)
    {
        _size = size;
    }

    private int _size;

    public int Size
    {
        get { return _size; }
        set { _size = value; }
    }
}

(Обратите внимание, что VB.NET не будет иметь этой проблемы, потому что в VB.NET все поля автоматически инициализируются значением 0 / null / false при первом создании.)

Это может показаться досадным ограничением при использовании автоматических свойств со структурами в C #. Подумав концептуально, мне было интересно, не будет ли это разумным местом для исключения, которое позволяет вызывать метод доступа к набору свойств в конструкторе структуры, по крайней мере, для автоматического свойства?

Это незначительная проблема, почти крайний случай, но мне было интересно, что другие думают об этом ...


person Mike Rosenblum    schedule 07.01.2009    source источник
comment
Поля в C # также инициализируются значениями 0 / null / false. Помните, что это делает среда выполнения, а не конкретный язык. ;)   -  person David Anderson    schedule 11.02.2009
comment
Не для полей структур в C #. Для структуры поля должны быть инициализированы явным конструктором или вызывающей стороной, если используется неявный конструктор без параметров. VB.NET не имеет этого ограничения, и поэтому приведенный выше пример, который не будет компилироваться на C #, будет компилироваться и нормально работать в VB.NET.   -  person Mike Rosenblum    schedule 26.03.2011


Ответы (3)


Начиная с C # 6 и далее: это больше не проблема


В Becore C # 6 вам нужно вызвать конструктор по умолчанию, чтобы это сработало:

public MyStruct(int size) : this()
{
    Size = size;
}

Более серьезная проблема здесь в том, что у вас есть изменяемая структура. Это никогда не рекомендуется. Я бы сделал это:

public int Size { get; private set; }

Не технически неизменяемый, но достаточно близкий.

В последних версиях C # вы можете улучшить это:

public int Size { get; }

Теперь это можно только назначить в конструкторе.

person Marc Gravell    schedule 07.01.2009
comment
Спасибо, Марк, это имеет смысл, спасибо, я забыл, что мы можем принудительно выполнить инициализацию по умолчанию. И я согласен, я бы никогда не сделал структуру изменяемой, но я отвечал на чей-то ответ ‹a href=stackoverflow.com/questions/414981/ ›. - person Mike Rosenblum; 07.01.2009
comment
Я думаю, что фраза никогда немного сильна. По умолчанию следует использовать неизменяемый, но делать вид, будто такой мандат - плохая идея. ;) - person Erich Mirabal; 14.05.2009
comment
Использование абсолютов никогда не является хорошей идеей ;-p (посмотрите, что я там делал ...). В самом деле, если вы понимаете последствия, изменяемые структуры можно использовать. Проблема в том, что большинство людей их не понимают, и на то, чтобы учиться, у них уходит много времени и разочарований. - person Marc Gravell; 14.05.2009
comment
@MarcGravell, почему изменяемая структура - это плохо? Не следует рассматривать мою структуру как любой другой POD (например, integer, double, float)? - person dotnetN00b; 17.02.2012
comment
@ dotnetN00b да, стоит! Подсказка: целые числа, числа с двойной точностью и числа с плавающей запятой неизменны: p нет проблем с повторным присвоением всего значения (кроме, возможно, атомарного в сценарии потоковой передачи); проблема заключается в том, что вы редактируете части одного значения на месте, что часто приводит к потере изменений, если вы не будете очень осторожны. - person Marc Gravell; 17.02.2012
comment
Техника, с помощью которой вы связываете :this(), считается самой лучшей и элегантной. Но можно написать и другими способами, например public MyStruct(int size) { this = new MyStruct { Size = size, }; }. Конечно, new MyStruct без аргументов конструктора действительно то же самое, что :this(). - person Jeppe Stig Nielsen; 18.11.2013
comment
@MarcGravell Если полностью исключить private set;, вы создадите истинное поддерживающее поле только для чтения и, таким образом, неизменяемую структуру. - person riezebosch; 06.07.2017
comment
@riezebosch теперь есть; этого не произошло, когда я написал этот ответ :) хотя на самом деле IIRC не помечает поле field только для чтения на уровне IL; предстоящий материал ref / struct только для чтения должен улучшить это в vFuture - person Marc Gravell; 06.07.2017
comment
@MarcGravell Я тестировал его с VS2017, и это так! И для .NET Core, и для .NET Full создается поле поддержки initonly. - person riezebosch; 06.07.2017
comment
@riezebosch достаточно честно - я проверил, и действительно: initonly - person Marc Gravell; 06.07.2017
comment
@riezebosch: Структуры не контролируют, являются ли они изменяемыми. Вы можете создать структуру, которая может быть изменена только путем копирования на нее другой структуры, но все структуры являются изменяемыми, если они хранятся в чем-то, что позволяет их изменять, и неизменяемыми, если они хранятся в чем-то, что не позволяет им быть . - person supercat; 06.07.2017
comment
@supercat Я не понимаю, о чем вы говорите. Когда вы используете public int MyProperty { get; }, вы получаете резервное поле private, которое помечено как initonly, поэтому оно может быть назначено только из конструктора. Как тебе это изменчиво? - person riezebosch; 17.07.2017
comment
@riezebosch: использование оператора присваивания для копирования значения структурного типа в переменную или поле того же типа приведет к изменению назначения путем копирования значений всех полей из источника. - person supercat; 17.07.2017
comment
@supercat Я думаю, большинство людей сказали бы, что в этом случае мутировал parent, а не структура. По вашему определению, целые числа будут считаться изменяемыми, что: они не (по крайней мере, в каком-либо значимом смысле) - person Marc Gravell; 17.07.2017
comment
@MarcGravell: все методы, определенные в Int32, ведут себя так, как если бы они вызывались по значению, но некоторые структурные методы - нет. Например, если KeyValuePair<T,U> изменяется во время выполнения его ToString() метода, его возвращаемое значение может отражать старое значение T и новое значение U. Такое невозможно с неизменяемым типом. - person supercat; 17.07.2017
comment
@supercat, вы говорите о разорванном чтении - или о чем-то очень похожем, включающем множественный доступ к полям для значения - это совершенно другая концепция изменчивости, и снова зависит от родителя. - person Marc Gravell; 17.07.2017
comment
@MarcGravell: не обязательно разорванное чтение, хотя у правильного неизменяемого типа не будет проблем с разорванным чтением. KeyValuePair<T,U> позвонит ToString на Key, а затем Value. Если вызов Key.ToString() перезаписывает поле, в котором был сохранен KeyValuePair<T,U>, вызов Value.ToSTring() отразит это изменение. Нет необходимости в многопоточности. - person supercat; 17.07.2017
comment
@supercat, да, будет; все, что превышает ширину выборки ЦП, подвержено такому поведению в условиях гонки. Вы можете получить эту проблему, например, для long на x86. Все сводится к выполнению одного атомарного чтения для любого доступа. Это мало связано с тем, является ли само значение неизменяемым, а скорее связано с тем, будет ли память когда-либо изменена на другое значение, которое сводится к следующему: является ли родительский и все вышестоящие родительские элементы также неизменными. Но: само значение по-прежнему неизменяемо (для любого полезного определения неизменяемого). - person Marc Gravell; 17.07.2017
comment
@MarcGravell: если classField относится к типу класса, вызов classField.someMethod() сделает снимок значения этого поля (т.е. ссылку, хранящуюся в нем) доступным для вызываемого метода как this; значение this не изменится во время этого метода. Если structField имеет тип структуры, вызов structField.someMethod передаст просмотр в реальном времени базового хранилища; значение this может быть изменено во время выполнения объектами, о которых метод ничего не знает. - person supercat; 17.07.2017
comment
@supercat радость this от того, что struct является _3 _.... но опять же, я понимаю проблему: я просто не согласен с тем, что это означает, что SomeType изменчив. Скорее, memory изменчив - но память всегда изменчива. - person Marc Gravell; 17.07.2017

Вы можете исправить это, сначала вызвав конструктор по умолчанию:

struct MyStruct 
{
    public MyStruct(int size) : this() 
    {
        this.Size = size; // <-- now works
    }

     public int Size { get; set; }
}
person Stormenet    schedule 07.01.2009

Еще один неясный способ решения этой проблемы - один, обнаруженный во временном классе Tuple в Managed Extensibility Framework (через Кшиштоф Козьмич):

public struct TempTuple<TFirst, TSecond>
{
    public TempTuple(TFirst first, TSecond second)
    {
        this = new TempTuple<TFirst, TSecond>(); // Kung fu!
        this.First = first;
        this.Second = second;
    }

    public TFirst First { get; private set; }
    public TSecond Second { get; private set; }

(Полный исходный код из Codeplex: Tuple.cs)

Я также отмечаю, что документация для CS0188 была обновлена, чтобы добавить:

Если вы видите эту ошибку при попытке инициализировать свойство в конструкторе структуры, решение состоит в том, чтобы изменить параметр конструктора, чтобы указать поле поддержки вместо самого свойства. В структурах следует избегать автоматически реализуемых свойств, поскольку они не имеют резервного поля и поэтому не могут быть инициализированы каким-либо образом из конструктора.

Я полагаю, что это означает, что официальное руководство - использовать свойства старого стиля в ваших структурах, когда вы сталкиваетесь с этой проблемой, которая, вероятно, менее неясна (и более удобочитаема), чем любая из двух других альтернатив, исследованных до сих пор.

person Daniel Fortunov    schedule 11.02.2009
comment
Спасибо за обновление отчета об ошибке. Действительно, наиболее эффективная ситуация для структур - это использование явного резервного поля. Если кто-то хочет инициализировать что-то другое, кроме 0 / null / false, тогда для этого требуется два шага, если нет поддерживающего поля: null out, а затем установить фактическое значение. - person Mike Rosenblum; 11.02.2009
comment
Я должен сказать, что линия кунг-фу действительно крутая. (Невозможно сделать с классом.) Но это то же самое, что объявить конструктор «public TempTuple (сначала TFirst, затем TSecond): this ()», что, на мой взгляд, является более чистым способом сделать это. . (И мы по-прежнему инициализируем каждое поле дважды.) - person Mike Rosenblum; 11.02.2009