Поделиться через


Конструкторы структуры без параметров

Заметка

Эта статья является спецификацией компонентов. Спецификация служит проектным документом для функции. Она включает предлагаемые изменения спецификации, а также информацию, необходимую во время проектирования и разработки функции. Эти статьи публикуются до тех пор, пока предложенные изменения спецификации не будут завершены и включены в текущую спецификацию ECMA.

Может возникнуть некоторое несоответствие между спецификацией компонентов и завершенной реализацией. Эти различия фиксируются в соответствующих записях о проектировании языка (LDM) на собраниях .

Дополнительные сведения о процессе внедрения спецификаций функций в стандарт языка C# см. в статье о спецификациях .

Проблема чемпиона: https://github.com/dotnet/csharplang/issues/99

Сводка

Поддержка конструкторов без параметров и инициализаторов полей экземпляра для типов структур.

Мотивация

Явные конструкторы без параметров дают больше контроля над минимально созданными экземплярами типа структуры. Инициализаторы полей экземпляра позволяют упростить процесс инициализации в нескольких конструкторах. Вместе они закроют очевидный разрыв между объявлениями struct и class.

Поддержка инициализаторов полей также позволяет инициализации полей в объявлениях record struct без явной реализации основного конструктора.

record struct Person(string Name)
{
    public object Id { get; init; } = GetNextId();
}

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

record struct Person()
{
    public string Name { get; init; }
    public object Id { get; init; } = GetNextId();
}

Предложение

Инициализаторы полей экземпляра

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

Как и при инициализаторах полей класса §15.5.6.3:

Инициализатор переменных для поля экземпляра не может ссылаться на созданный экземпляр.

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

struct S { int F = 42; } // error: 'struct' with field initializers must include an explicitly declared constructor

Конструкторы

Структура может объявлять конструктор экземпляра без параметров.

Конструктор экземпляра без параметров действителен для всех типов структур, включая struct, readonly struct, ref structи record struct.

Если конструктор экземпляра без параметров не объявлен, структура (см. §16.4.9) ...

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

Модификаторы

Конструктор структуры без параметров экземпляра должен быть объявлен public.

struct S0 { }                   // ok
struct S1 { public S1() { } }   // ok
struct S2 { internal S2() { } } // error: parameterless constructor must be 'public'

Недоступные конструкторы игнорируются при импорте типов из метаданных.

Конструкторы можно объявить extern или unsafe. Конструкторы не могут быть partial.

Выполнение инициализаторов полей

Инициализаторы переменных экземпляров (§15.11.3) изменены следующим образом:

Если конструктор экземпляра класса не имеет инициализатора конструктора или имеет инициализатор конструктора формы base(...), то этот конструктор неявно выполняет инициализации, указанные инициализаторами переменных полей экземпляра, объявленных в его классе. Это соответствует последовательности назначений, которые выполняются сразу после входа в конструктор и перед неявным вызовом конструктора прямого базового класса.

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

Когда конструктор экземпляра структуры имеет инициализатор конструктора this(), представляющий конструктор по умолчанию без параметров, объявленный конструктор неявно обнуляет все поля экземпляра и производит инициализацию, заданную параметрами variable_initializerдля полей экземпляра, объявленных в его структуре. Сразу после входа в конструктор все поля типа значения задаются по умолчанию, а для всех полей ссылочного типа задано значение null. Сразу после этого выполняется последовательность назначений, соответствующих инициализаторам переменных в форме variable_initializer.

Определенное назначение

Поля экземпляров (кроме полей fixed) должны быть определенно назначены в конструкторах экземпляров структуры, которые не имеют инициализатора this() (см. §16.4.9).

struct S0 // ok
{
    int x;
    object y;
}

struct S1 // error: 'struct' with field initializers must include an explicitly declared constructor
{
    int x = 1;
    object y;
}

struct S2
{
    int x = 1;
    object y;
    public S2() { } // error in C# 10 (valid starting in C# 11): field 'y' must be assigned
}

struct S3 // ok
{
    int x = 1;
    object y;
    public S3() { y = 2; }
}

Нет инициализатора base()

Инициализатор base() запрещен в конструкторах структур.

Компилятор не будет вызывать базовый конструктор System.ValueType из конструкторов экземпляров структуры.

record struct

Сообщается об ошибке, если record struct имеет инициализаторы полей и не содержит основного конструктора или конструкторов экземпляров, так как инициализаторы полей не будут выполняться.

record struct R0;                  // ok
record struct R1 { int F = 42; }   // error: 'struct' with field initializers must include an explicitly declared constructor
record struct R2() { int F = 42; } // ok
record struct R3(int F);           // ok

record struct с пустым списком параметров будет иметь первичный конструктор без параметров.

record struct R3();                // primary .ctor: public R3() { }
record struct R4() { int F = 42; } // primary .ctor: public R4() { F = 42; }

Явный конструктор без параметров в record struct должен иметь инициализатор this, который вызывает основной конструктор или явно объявленный конструктор.

record struct R5(int F)
{
    public R5() { }                  // error: must have 'this' initializer that calls explicit .ctor
    public R5(object o) : this() { } // ok
    public int F =  F;
}

Поля

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

struct S0
{
    public S0() { }
}

struct S1
{
    S0 F; // S0 constructor ignored
}

struct S<T> where T : struct
{
    T F; // constructor (if any) ignored
}

выражение default

default игнорирует конструктор без параметров и создает экземпляр с обнулёнными полями. Нет изменений с C#9.

// struct S { public S() { } }

_ = default(S); // constructor ignored, no warning

new()

При создании объекта вызывается конструктор без параметров, если он открыт; в противном случае экземпляр равен нулю. Нет изменений с C#9.

// public struct PublicConstructor { public PublicConstructor() { } }
// public struct PrivateConstructor { private PrivateConstructor() { } }

_ = new PublicConstructor();  // call PublicConstructor::.ctor()
_ = new PrivateConstructor(); // initobj PrivateConstructor

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

struct S { public S(int i) { } }
static T CreateNew<T>() where T : new() => new T();

_ = new S();        // no warning called
_ = CreateNew<S>(); // ok

Неинициализированные значения

Локальная переменная или поле структурного типа, которое не инициализировано явно, обнуляется. Компилятор сообщает определенную ошибку назначения для неинициализированной структуры, которая не пуста. Нет изменений с C#9.

NoConstructor s1;
PublicConstructor s2;
s1.ToString(); // error: use of unassigned local (unless type is empty)
s2.ToString(); // error: use of unassigned local (unless type is empty)

Выделение массива

Выделение массива игнорирует любой конструктор без параметров и создает ноль элементов. Нет изменений с C#9.

// struct S { public S() { } }

var a = new S[1]; // constructor ignored, no warning

Значение параметра по умолчанию new()

Значение параметра по умолчанию new() привязывается к конструктору без параметров, если общедоступный (и сообщает об ошибке, что значение не является константой); в противном случае экземпляр равен нулю. Нет изменений с C#9.

// public struct PublicConstructor { public PublicConstructor() { } }
// public struct PrivateConstructor { private PrivateConstructor() { } }

static void F1(PublicConstructor s1 = new()) { }  // error: default value must be constant
static void F2(PrivateConstructor s2 = new()) { } // ok: initobj

Ограничения параметров типа: new() и struct

Ограничения параметров типа new() и struct требуют, чтобы конструктор без параметров был public, если он определен (см. ограничения соответствия — §8.4.5).

Компилятор предполагает, что все структуры удовлетворяют ограничениям new() и struct. Нет изменений с C#9.

// public struct PublicConstructor { public PublicConstructor() { } }
// public struct InternalConstructor { internal InternalConstructor() { } }

static T CreateNew<T>() where T : new() => new T();
static T CreateStruct<T>() where T : struct => new T();

_ = CreateNew<PublicConstructor>();      // ok
_ = CreateStruct<PublicConstructor>();   // ok

_ = CreateNew<InternalConstructor>();    // compiles; may fail at runtime
_ = CreateStruct<InternalConstructor>(); // compiles; may fail at runtime

new T() генерируется как вызов System.Activator.CreateInstance<T>(), и компилятор предполагает, что реализация CreateInstance<T>() вызывает конструктор без параметров public, если он определен.

в .NET Framework Activator.CreateInstance<T>() вызывает конструктор без параметров, если ограничение where T : new(), но, как представляется, игнорирует конструктор без параметров, если ограничение where T : struct.

Необязательные параметры

Конструкторы с необязательными параметрами не считаются конструкторами без параметров. Нет изменений с C#9.

struct S1 { public S1(string s = "") { } }
struct S2 { public S2(params object[] args) { } }

_ = new S1(); // ok: ignores constructor
_ = new S2(); // ok: ignores constructor

Метаданные

Явные безпараметрические конструкторы структур будут создаваться в метаданных.

Открытые конструкторы экземпляров без параметров будут импортированы из метаданных; не являющиеся открытыми конструкторы экземпляров будут игнорироваться. Нет изменений с C#9.

См. также

Совещания по дизайну