Почему я не могу определить конструктор по умолчанию для структуры in.NET?

в .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 не по умолчанию RationalS (и хотите оптимизировать 1000 конструкций) я буду использовать List<Rational> вместо массива.

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

10 ответов


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


EDIT: я отредактировал ответ ниже из-за понимания Grauenwolf в CLR.

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

MyStruct[] foo = new MyStruct[1000];

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

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

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

Rational[] fractions = new Rational[1000];

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

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

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

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


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

MyClass m;
MyStruct m2;

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


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

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

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

var rat = Rational.One;

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

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

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


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

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

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

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

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

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


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


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

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


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

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#, вы можете попробовать/поймать/наконец, для работы с ошибками, где конкретный тип данных не инициализируется конструктором по умолчанию Point2D(). Я думаю, что это неуловимо, как решение некоторых люди на этот ответ. Именно поэтому я добавляю свой. Использование функций getter и setter в C# позволит вам обойти этот конструктор по умолчанию без смысла и попытаться поймать то, что вы не инициализировали. Для меня это отлично работает, для кого-то еще вы можете добавить некоторые операторы if. Таким образом, в случае, когда вам нужна настройка числителя/знаменателя, этот код может помочь. Я просто хотел бы повторить, что это решение не выглядит хорошо, вероятно, работает еще хуже от с точки зрения эффективности, но для кого-то из более старой версии 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;
    }
}

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

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

это решение работает для простых структур только с типами значений (без типа ref или nullable struct).

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

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


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

    public Rational(long num = 0, long denom = 1)   // This is allowed!!!
    {
        numerator   = num;
        denominator = denom;
    }
}