С# - вызов конструктора struct, который имеет все дефолтные параметры

Сегодня я столкнулся с этой проблемой при создании 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).
  • В. Измените, как значения по умолчанию заполняются для конструктора ExampleStruct().
  • С. Некоторые другие варианты/варианты.

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

public readonly int Value;

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

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

Ответы

Ответ 1

Собственно, MSDN имеет хорошие рекомендации по struct

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

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

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

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

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

Его не нужно часто вставлять в бокс.

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

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

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

Помните, что struct должен работать так, что new X() == default(X), то есть вновь построенный struct будет содержать значения по умолчанию для всех полей этого struct. Это довольно очевидно, так как С# не позволит вам определить конструктор без параметров для 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), поэтому, если это правда, мы еще не установили его и можем использовать оператор null-coalescing для возврата по умолчанию вместо 1. Если мы установили его в конструкторе, он будет не нулевым и вместо этого примет это значение...

Ответ 2

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

default(ExampleStruct)

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

new ExampleStruct()

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

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

public struct ExampleStruct
{
  readonly int valueMinusOne;

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

  public ExampleStruct(int value)
  {
    valueMinusOne = value - 1;
  }
}

Ответ 3

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

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

Ответ 4

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

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

2015 Addendum

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

Ответ 5

почему у вас есть public ExampleStruct(int value = 1) : this()?

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