C# — вызов конструктора структуры со всеми параметрами по умолчанию

Я столкнулся с этой проблемой сегодня при создании struct для хранения набора данных. Вот пример:

public struct ExampleStruct
{
    public int Value { get; private set; }

    public ExampleStruct(int value = 1)
        : this()
    {
        Value = value;
    }
}

Выглядит хорошо и денди. Проблема заключается в том, что я пытаюсь использовать этот конструктор без указания значения и хочу использовать значение по умолчанию 1 для параметра:

private static void Main(string[] args)
{
    ExampleStruct example1 = new ExampleStruct();

    Console.WriteLine(example1.Value);
}

Этот код выводит 0 и не выводит 1. Причина в том, что все структуры имеют общедоступные конструкторы без параметров. Итак, подобно тому, как я вызываю this() свой явный конструктор, в Main происходит то же самое, когда new ExampleStruct() фактически вызывает ExampleStruct(), но не вызывает ExampleStruct(int value = 1). Поскольку он это делает, он использует значение int по умолчанию, равное 0, как Value.

Что еще хуже, мой фактический код проверяет, находится ли параметр int value = 1 в допустимом диапазоне внутри конструктора. Добавьте это в конструктор ExampleStruct(int value = 1) выше:

if(value < 1 || value > 3)
{
    throw new ArgumentException("Value is out of range");
}

Итак, на данный момент конструктор по умолчанию фактически создал объект, который недействителен в том контексте, для которого он мне нужен. Кто-нибудь знает, как я могу:

  • A. Вызовите конструктор ExampleStruct(int value = 1).
  • B. Измените способ заполнения значений по умолчанию для конструктора ExampleStruct().
  • C. Некоторые другие предложения/варианты.

Кроме того, я знаю, что мог бы использовать такое поле вместо моего свойства Value:

public readonly int Value;

Но моя философия заключается в том, чтобы использовать поля в частном порядке, если они не const или static.

Наконец, причина, по которой я использую struct вместо class, заключается в том, что это просто объект для хранения неизменяемых данных, который должен быть полностью заполнен при его построении и при передаче в качестве параметра не должен быть в состоянии быть null (поскольку он передается по значению как struct), для чего и предназначены структуры.


person Michael Yanni    schedule 14.11.2012    source источник
comment
Это не обязательно полная причина для использования struct, вы все равно можете сделать все это с class и просто бросить ArgumentNullException. Основные причины использования strcut описаны в MSDN: msdn.microsoft.com/ en-us/library/ms229017.aspx   -  person James Michael Hare    schedule 15.11.2012
comment
Когда-нибудь я должен написать обо всех СУМАСШЕДШИХ примерах использования структур! спасибо за это!   -  person Carlo V. Dango    schedule 15.11.2012
comment
обратите внимание, что при каждом вызове вы делаете копию своих данных, поскольку это структура.   -  person Carlo V. Dango    schedule 15.11.2012
comment
Вместо того, чтобы иметь диапазон 1-3, иметь диапазон 0-2 и иметь свойство, которое изменяет возвращаемое значение, чтобы оно находилось в правильном диапазоне;)   -  person porges    schedule 15.11.2012
comment
@Porges: вы могли бы сделать это, но это было бы довольно грязно и должно быть хорошо задокументировано. Я предпочитаю инициализированное поле с нулевым значением, а не значение по умолчанию для свойства доступа.   -  person James Michael Hare    schedule 15.11.2012


Ответы (5)


На самом деле в MSDN есть хорошие рекомендации по struct

Рассмотрите возможность определения структуры вместо класса, если экземпляры типа малы и обычно недолговечны или обычно встроены в другие объекты.

Не определяйте структуру, если тип не обладает всеми следующими характеристиками:

Он логически представляет одиночное значение, подобное примитивным типам (целое число, двойное число и т. д.).

Его размер экземпляра меньше 16 байт.

Это неизменно.

Его не нужно будет часто упаковывать.

Обратите внимание, что они являются соображениями для рассмотрения struct, а не "это должно всегда быть структурой". Это связано с тем, что выбор использования struct может иметь последствия для производительности и использования (как положительные, так и отрицательные), и его следует выбирать тщательно.

Обратите внимание, в частности, что они не рекомендуют struct для вещей > 16 байт (тогда стоимость копирования становится дороже, чем копирование ссылки).

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

Помните, что struct должен работать таким образом, что new X() == default(X), то есть вновь созданный struct будет содержать значения по умолчанию для всех полей этого struct. Это довольно очевидно, поскольку C# не позволит вам определить конструктор без параметров для struct, хотя любопытно, что они позволяют принимать значения по умолчанию для всех аргументов без предупреждения.

Таким образом, я действительно предлагаю вам придерживаться class и сделать его неизменяемым и просто проверять наличие null в методах, которым он передается.

public class ExampleClass
{
    // have property expose the "default" if not yet set
    public int Value { get; private set; }

    // remove default, doesn't work
    public ExampleStruct(int value)
    {
        Value = value;
    }
}

Тем не менее, если вам необходимо иметь struct по другим причинам, но, пожалуйста, учитывайте затраты на struct, такие как копирование и т. д., вы можете сделать следующее:

public struct ExampleStruct
{
    private int? _value;

    // have property expose the "default" if not yet set
    public int Value
    {
        get { return _value ?? 1; }
    }

    // remove default, doesn't work
    public ExampleStruct(int value)
        : this()
    {
        _value = value;
    }
}

Обратите внимание, что по умолчанию Nullable<int> будет null (то есть HasValue == false), поэтому, если это правда, мы еще не установили его и можем использовать оператор объединения с нулевым значением, чтобы вместо этого вернуть наше значение по умолчанию 1. Если мы установили его в конструкторе, оно будет ненулевым и вместо этого примет это значение...

person James Michael Hare    schedule 14.11.2012
comment
Спасибо, Джеймс. В итоге я изменил его на class. Методы, которые имеют ExampleClass в качестве параметра, по умолчанию имеют значение ExampleClass example = null. Я помещаю документацию, в которой говорится, что если установлено значение null, по умолчанию используется значение example ?? new ExampleClass(). Поскольку теперь это класс, он в конечном итоге вызывает правильный конструктор public ExampleClass(int value = 1). - person Michael Yanni; 15.11.2012
comment
Хороший совет по оператору объединения ? и null. Это дало мне решение для Size.Empty, где Height и Width могли иметь значение по умолчанию Double.NaN ... таким образом, действительно пустое Size. - person IAbstract; 25.08.2015

Я не думаю, что это хороший дизайн, чтобы иметь struct ExampleStruct такое, что

default(ExampleStruct)

т. е. значение, в котором все поля экземпляра равны нулю/ложи/нулю, не является допустимым значением структуры. И, как вы знаете, когда вы говорите

new ExampleStruct()

это точно так же, как default(ExampleStruct), и дает вам значение вашей структуры, где все поля (включая «сгенерированные» поля из автоматических свойств) равны нулю.

Возможно, вы могли бы сделать это:

public struct ExampleStruct
{
  readonly int valueMinusOne;

  public int Value { get { return valueMinusOne + 1; } }

  public ExampleStruct(int value)
  {
    valueMinusOne = value - 1;
  }
}
person Jeppe Stig Nielsen    schedule 14.11.2012
comment
"I don't think it's good design to have a struct ExampleStruct such that" [...]" На самом деле, это не только плохой дизайн, это даже невозможно; язык не позволит вам делать что-либо еще. - person Servy; 15.11.2012
comment
почему бы не установить пустой конструктор в 1? - person Carlo V. Dango; 15.11.2012
comment
@CarloV.Dango Попробуйте сделать это и убедитесь сами. Вы получите ошибку компилятора. - person Servy; 15.11.2012
comment
Использование структур в C# похоже на программирование на C++, фу! - person Carlo V. Dango; 15.11.2012
comment
@Servy Я знаю, что значение по умолчанию всегда равно нулю. Я говорю, что плохо считать это значение по умолчанию недействительным. Моя расширенная версия ответа пытается перевести значение таким образом, чтобы значение по умолчанию 1 сохранялось внутри как ноль. Что-то вроде структуры DateTime, где default(DateTime) (с нулевым полем Ticks) представляет 0001-01-01. - person Jeppe Stig Nielsen; 15.11.2012

Я предполагаю, что компилятор на самом деле выбирает автоматический ctor по умолчанию для структур здесь http://msdn.microsoft.com/en-us/library/aa288208(v=vs.71).aspx вместо использования ctor со значениями по умолчанию.

добавлены ссылки: http://csharpindepth.com/Articles/General/Overloading.aspx ( Раздел необязательных параметров)

person fsimonazzi    schedule 14.11.2012
comment
7.5.3.2 Улучшенный функциональный член в спецификации C#. - person fsimonazzi; 15.11.2012
comment
Я просто говорю, что вы должны были включить это в свой ответ. это явно правильно, просто это плохо объяснено. - person Servy; 15.11.2012
comment
Справедливо. Кстати, то же самое касается классов, только вам нужно явно добавить ctor по умолчанию. - person fsimonazzi; 15.11.2012
comment
Точная формулировка в этой спецификации: В противном случае, если все параметры MP имеют соответствующий аргумент, тогда как аргументы по умолчанию необходимо заменить хотя бы одним необязательным параметром в MQ, тогда MP лучше, чем MQ. - person Jeppe Stig Nielsen; 15.11.2012

Хотя некоторые языки (если не что иное, CIL) позволяют определить конструктор структуры таким образом, что new T() имеет поля, установленные на что-то отличное от их значений по умолчанию «все нули», C# и vb.net (и, вероятно, большинство других языков) придерживаются мнения, что поскольку такие вещи, как элементы массива и поля класса, всегда должны быть инициализированы значением default(T), и поскольку их инициализация чем-то, что не соответствует new T(), может привести к путанице, структуры не должны определять new T() как нечто иное, чем default(T).

Я бы предположил, что вместо того, чтобы пытаться сглазить параметр по умолчанию в параметризованном конструкторе, вы просто определяете свойство статической структуры, которое возвращает желаемое значение по умолчанию, и заменяете каждое вхождение new ExampleStruct() на ExampleStruct.NiceDefault;.

Дополнение 2015 г.

Похоже, что C# и VB.NET могут ослабить запрет на определение конструкторов структур без параметров. Это может привести к тому, что оператор типа Dim s = New StructType() присвоит s значение, отличное от значения, присвоенного новым элементам массива типа StructType. Я не очень в восторге от этого изменения, учитывая, что new StructType часто используется в местах, где что-то похожее на default(StructType) в C# было бы более подходящим, если бы оно существовало. VB.NET разрешил бы Dim s As StructType = Nothing, но это выглядит довольно глупо.

person supercat    schedule 14.11.2012

зачем тебе public ExampleStruct(int value = 1) : this() ?

разве это не должно быть public ExampleStruct(int value = 1)? Я думаю, что :this() создает конструктор без параметров.

person gashach    schedule 14.11.2012
comment
Вам было бы полезно более четко понять, как : this() относится к вызову конструктора по умолчанию. - person EtherDragon; 15.11.2012
comment
Обратите внимание, что его структура использует автоматические свойства. Они вводят специальные резервные поля. Поэтому определяемые пользователем конструкторы должны связывать this() для компиляции. - person Jeppe Stig Nielsen; 15.11.2012