Почему я не могу определить конструктор по умолчанию для структуры 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#, a по умолчанию рациональным является 0/0 что не так круто.



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





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




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



 Rational[] fractions = new Rational[1000];


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




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



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

618   10  

10 ответов:

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


EDIT: я отредактировал ответ ниже из-за понимания Grauenwolf в 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 к тому, что вы получаете, Если вы вызываете обычный конструктор. Смотрите это так вопрос немного больше об этом аспекте.

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

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

MyClass m;
MyStruct m2;

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

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

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

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

var rat = Rational.One;

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

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

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

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

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

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

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

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

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

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

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

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

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

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# позволит вам обойти этот конструктор по умолчанию без смысла и поставить try catch вокруг того, что вы не инициализировали. Для меня это отлично работает, для кого-то еще вы можете добавить некоторые операторы 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 структуры).

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;
    }
}
public struct Rational 
{
    private long numerator;
    private long denominator;

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

Comments

    Ничего не найдено.