Почему я не могу определить конструктор по умолчанию для структуры в .NET? - PullRequest
228 голосов
/ 02 декабря 2008

В .NET тип значения (C # struct) не может иметь конструктора без параметров. Согласно этой записи это предписано спецификацией CLI. Что происходит, так это то, что для каждого типа значения создается конструктор по умолчанию (компилятором?), Который инициализирует все члены нулем (или null).

Почему запрещено определять такой конструктор по умолчанию?

Одно тривиальное использование для рациональных чисел:

public struct Rational {
    private long numerator;
    private long denominator;

    public Rational(long num, long denom)
    { /* Todo: Find GCD etc. */ }

    public Rational(long num)
    {
        numerator = num;
        denominator = 1;
    }

    public Rational() // This is not allowed
    {
        numerator = 0;
        denominator = 1;
    }
}

При использовании текущей версии C # Rational по умолчанию - 0/0, что не так круто.

PS : помогут ли параметры по умолчанию решить эту проблему для C # 4.0 или будет вызван определяемый CLR конструктор по умолчанию?


Джон Скит ответил:

Если использовать ваш пример, что бы вы хотели, чтобы произошло, когда кто-то сделал:

 Rational[] fractions = new Rational[1000];

Должен ли он проходить через ваш конструктор 1000 раз?

Конечно, так и должно быть, поэтому я написал конструктор по умолчанию. CLR должен использовать конструктор по умолчанию обнуления , если явно не определен конструктор по умолчанию; Таким образом, вы платите только за то, что используете. Тогда, если мне нужен контейнер из 1000 нестандартных Rational с (и я хочу оптимизировать 1000 конструкций), я буду использовать List<Rational> вместо массива.

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

Ответы [ 10 ]

178 голосов
/ 02 декабря 2008

Примечание: ответ ниже был написан задолго до C # 6, который планирует ввести возможность объявлять конструкторы без параметров в структурах - но они все равно не будут вызываться в все ситуации (например, для создания массива) (в конце эта функция не была добавлена ​​в C # 6 ).


РЕДАКТИРОВАТЬ: я отредактировал ответ ниже из-за понимания Грауенвольфом CLR.

CLR позволяет типам значений иметь конструкторы без параметров, а C # - нет. Я полагаю, что это потому, что это привело бы к ожиданию, что конструктор будет вызван, когда это не так. Например, рассмотрим это:

MyStruct[] foo = new MyStruct[1000];

CLR может сделать это очень эффективно, просто выделив соответствующую память и обнулив все это. Если бы ему пришлось запускать конструктор MyStruct 1000 раз, это было бы намного менее эффективно. (На самом деле это не так - если у вас do имеется конструктор без параметров, он не запускается при создании массива или при наличии неинициализированной переменной экземпляра.)

Основное правило в C # - «значение по умолчанию для любого типа не может полагаться ни на какую инициализацию». Теперь они могли позволять определять конструкторы без параметров, но тогда не требовали, чтобы этот конструктор выполнялся во всех случаях - но это привело бы к еще большей путанице. (Или, по крайней мере, поэтому я считаю, что аргумент идет.)

РЕДАКТИРОВАТЬ: Чтобы использовать ваш пример, что бы вы хотели, чтобы произошло, когда кто-то сделал:

Rational[] fractions = new Rational[1000];

Должен ли он проходить через ваш конструктор 1000 раз?

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

EDIT: (отвечая немного больше на вопрос) Конструктор без параметров не создается компилятором. Типы значений не обязательно должны иметь конструкторы в отношении CLR - хотя оказывается, что может , если вы пишете это в IL. Когда вы пишете "new Guid()" в C #, это излучает другой IL, чем вы получаете, если вы вызываете обычный конструктор. См. этот SO вопрос , чтобы узнать больше об этом аспекте.

Я подозреваю , что в структуре нет никаких типов значений с конструкторами без параметров. Без сомнения, NDepend мог бы сказать мне, если бы я спросил это достаточно хорошо ... Тот факт, что C # запрещает это, является достаточно большим намеком, чтобы я подумал, что это, вероятно, плохая идея.

42 голосов
/ 02 декабря 2008

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

MyClass m;
MyStruct m2;

Если вы объявите два поля, как указано выше, не создавая ни одного из них, а затем остановите отладчик, m будет нулевым, а m2 - нет. Учитывая это, конструктор без параметров не имеет смысла, фактически любой конструктор в структуре - это присвоение значений, сама вещь уже существует, просто объявив ее. Действительно, m2 вполне можно было бы использовать в приведенном выше примере и вызывать его методы, если они есть, и манипулировать их полями и свойствами!

14 голосов
/ 19 января 2015

Хотя CLR позволяет это, C # не позволяет структурам иметь конструктор по умолчанию без параметров. Причина в том, что для типа значения компиляторы по умолчанию не генерируют конструктор по умолчанию и не генерируют вызов конструктора по умолчанию. Таким образом, даже если вы определили конструктор по умолчанию, он не будет вызываться, и это вас только запутает.

Чтобы избежать таких проблем, компилятор C # запрещает определение конструктора по умолчанию пользователем. И поскольку он не генерирует конструктор по умолчанию, вы не можете инициализировать поля при их определении.

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

Вам не нужно создавать экземпляр структуры с ключевым словом new. Вместо этого он работает как int; Вы можете напрямую получить к нему доступ.

Структуры не могут содержать явных конструкторов без параметров. Члены структуры автоматически инициализируются до значений по умолчанию.

Конструктор по умолчанию (без параметров) для структуры может устанавливать значения, отличные от состояния с нулевым состоянием, что может привести к неожиданному поведению. Поэтому среда выполнения .NET запрещает конструкторы по умолчанию для структуры.

14 голосов
/ 11 января 2009

Вы можете создать статическое свойство, которое инициализирует и возвращает «рациональное» число по умолчанию:

public static Rational One => new Rational(0, 1); 

И используйте это как:

var rat = Rational.One;
13 голосов
/ 02 декабря 2008

Краткое объяснение:

В C ++ struct и class были просто двумя сторонами одной медали. Единственное реальное отличие состоит в том, что по умолчанию одно было общедоступным, а другое частным.

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

3 голосов
/ 03 декабря 2008

Просто особый случай. Если вы видите числитель 0 и знаменатель 0, сделайте вид, что он имеет значения, которые вы действительно хотите.

1 голос
/ 27 сентября 2017

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

используйте смещения для перемещения значений по умолчанию 0 в любое значение, которое вам нравится. здесь свойства должны использоваться вместо прямого доступа к полям. (возможно, с возможной функцией c # 7 вы лучше определите поля области свойств, чтобы они оставались защищенными от прямого доступа в коде.)

Это решение работает для простых структур только с типами значений (без типа ref или структуры, допускающей обнуление).

public struct Tempo
{
    const double DefaultBpm = 120;
    private double _bpm; // this field must not be modified other than with its property.

    public double BeatsPerMinute
    {
        get => _bpm + DefaultBpm;
        set => _bpm = value - DefaultBpm;
    }
}

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

пример с перечислениями в качестве поля.

public struct Difficaulty
{
    Easy,
    Medium,
    Hard
}

public struct Level
{
    const Difficaulty DefaultLevel = Difficaulty.Medium;
    private Difficaulty _level; // this field must not be modified other than with its property.

    public Difficaulty Difficaulty
    {
        get => _level + DefaultLevel;
        set => _level = value - DefaultLevel;
    }
}

Как я уже сказал, этот прием может работать не во всех случаях, даже если структура имеет только поля значений, только вы знаете, что работает в вашем случае или нет. просто изучить. но вы получите общее представление.

1 голос
/ 13 ноября 2016

Вот мое решение дилеммы конструктора по умолчанию. Я знаю, что это запоздалое решение, но я думаю, что стоит отметить, что это решение.

public struct Point2D {
    public static Point2D NULL = new Point2D(-1,-1);
    private int[] Data;

    public int X {
        get {
            return this.Data[ 0 ];
        }
        set {
            try {
                this.Data[ 0 ] = value;
            } catch( Exception ) {
                this.Data = new int[ 2 ];
            } finally {
                this.Data[ 0 ] = value;
            }
        }
    }

    public int Z {
        get {
            return this.Data[ 1 ];
        }
        set {
            try {
                this.Data[ 1 ] = value;
            } catch( Exception ) {
                this.Data = new int[ 2 ];
            } finally {
                this.Data[ 1 ] = value;
            }
        }
    }

    public Point2D( int x , int z ) {
        this.Data = new int[ 2 ] { x , z };
    }

    public static Point2D operator +( Point2D A , Point2D B ) {
        return new Point2D( A.X + B.X , A.Z + B.Z );
    }

    public static Point2D operator -( Point2D A , Point2D B ) {
        return new Point2D( A.X - B.X , A.Z - B.Z );
    }

    public static Point2D operator *( Point2D A , int B ) {
        return new Point2D( B * A.X , B * A.Z );
    }

    public static Point2D operator *( int A , Point2D B ) {
        return new Point2D( A * B.Z , A * B.Z );
    }

    public override string ToString() {
        return string.Format( "({0},{1})" , this.X , this.Z );
    }
}

игнорируя тот факт, что у меня есть статическая структура с именем null (Примечание: это только для всех положительных квадрантов), используя get; set; в C # вы можете использовать try / catch / finally для обработки ошибок, когда конкретный тип данных не инициализируется конструктором по умолчанию Point2D (). Я думаю, что это неуловимо как решение для некоторых людей на этот ответ. В основном это то, почему я добавляю свои. Использование функциональности get и set в C # позволит вам обойти этот конструктор по умолчанию без смысла и попытаться поймать то, что вы не инициализировали. Для меня это работает нормально, для кого-то еще вы можете добавить некоторые операторы if. Итак, в случае, если вы хотите установить Numerator / Denominator, этот код может помочь. Я просто хотел бы повторить, что это решение не выглядит красиво, возможно, работает еще хуже с точки зрения эффективности, но для тех, кто выходит из более старой версии C #, использование типов данных массива дает вам эту функциональность. Если вы просто хотите что-то, что работает, попробуйте это:

public struct Rational {
    private long[] Data;

    public long Numerator {
        get {
            try {
                return this.Data[ 0 ];
            } catch( Exception ) {
                this.Data = new long[ 2 ] { 0 , 1 };
                return this.Data[ 0 ];
            }
        }
        set {
            try {
                this.Data[ 0 ] = value;
            } catch( Exception ) {
                this.Data = new long[ 2 ] { 0 , 1 };
                this.Data[ 0 ] = value;
            }
        }
    }

    public long Denominator {
        get {
            try {
                return this.Data[ 1 ];
            } catch( Exception ) {
                this.Data = new long[ 2 ] { 0 , 1 };
                return this.Data[ 1 ];
            }
        }
        set {
            try {
                this.Data[ 1 ] = value;
            } catch( Exception ) {
                this.Data = new long[ 2 ] { 0 , 1 };
                this.Data[ 1 ] = value;
            }
        }
    }

    public Rational( long num , long denom ) {
        this.Data = new long[ 2 ] { num , denom };
        /* Todo: Find GCD etc. */
    }

    public Rational( long num ) {
        this.Data = new long[ 2 ] { num , 1 };
        this.Numerator = num;
        this.Denominator = 1;
    }
}
1 голос
/ 03 декабря 2008

Вы не можете определить конструктор по умолчанию, потому что вы используете C #.

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

0 голосов
/ 17 августа 2018
public struct Rational 
{
    private long numerator;
    private long denominator;

    public Rational(long num = 0, long denom = 1)   // This is allowed!!!
    {
        numerator   = num;
        denominator = denom;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...