Compartir a través de


Constructores de estructura sin parámetros

Nota

Este artículo es una especificación de características. La especificación actúa como documento de diseño de la característica. Incluye cambios de especificación propuestos, junto con la información necesaria durante el diseño y el desarrollo de la característica. Estos artículos se publican hasta que se finalizan los cambios de especificación propuestos e se incorporan en la especificación ECMA actual.

Puede haber algunas discrepancias entre la especificación de características y la implementación completada. Esas diferencias se recogen en las notas de la reunión de diseño de lenguaje (LDM) correspondientes.

Puede obtener más información sobre el proceso de adopción de especificaciones de características en el estándar del lenguaje C# en el artículo sobre las especificaciones de .

Problema planteado por experto: https://github.com/dotnet/csharplang/issues/99

Resumen

Admite constructores sin parámetros e inicializadores de campo de instancia para tipos de estructura.

Motivación

Los constructores sin parámetros explícitos darían más control sobre las instancias mínimamente construidas del tipo de estructura. Los inicializadores de campo de instancia permitirían la inicialización simplificada entre varios constructores. Conjuntamente, se cerraría una brecha obvia entre las declaraciones struct y class.

La compatibilidad con inicializadores de campo también permitiría la inicialización de campos en record struct declaraciones sin implementar explícitamente el constructor principal.

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

Si se admiten inicializadores de campo de estructura para constructores con parámetros, parece natural ampliarlo también a constructores sin parámetros.

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

Propuesta

Inicializadores de campos de instancia

Las declaraciones de campo de instancia para una estructura pueden incluir inicializadores.

Al igual que con los inicializadores de campo de clase §15.5.6.3:

Un inicializador de variable para un campo de instancia no puede hacer referencia a la instancia que se está creando.

Se notifica un error si una estructura tiene inicializadores de campo y ningún constructor de instancia declarado, ya que no se ejecutarán los inicializadores de campo.

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

Constructores

Un struct puede declarar un constructor de instancia sin parámetros.

Un constructor de instancia sin parámetros es válido para todos los tipos de estructura, incluidos struct, readonly struct, ref structy record struct.

Si no se declara ningún constructor de instancia sin parámetros, el struct (consulte la sección 16.4.9) ...

implícitamente tiene un constructor de instancia sin parámetros que siempre devuelve el valor resultante de establecer todos los campos de tipo de valor en su valor predeterminado y todos los campos de tipo de referencia en null.

Modificadores

Un constructor de estructura de instancia sin parámetros debe declararse public.

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

Los constructores no públicos se omiten al importar tipos de metadatos.

Los constructores pueden declararse extern o unsafe. Los constructores no pueden ser partial.

Ejecución de inicializadores de campo

Inicializadores de variables de instancia (sección 15.11.3) se modifica como sigue:

Cuando un constructor de instancia de una clase no tiene inicializador de constructor, o tiene un inicializador de constructor de la forma base(...), ese constructor realiza implícitamente las inicializaciones especificadas por los variable_initializer de los campos de instancia declarados en su clase. Esto corresponde a una secuencia de asignaciones que se ejecutan inmediatamente después de la entrada al constructor y antes de la invocación implícita del constructor de clase base directa.

Cuando un constructor de instancia struct no tiene inicializador de constructor, ese constructor realiza implícitamente las inicializaciones especificadas por los variable_initializer de los campos de instancia declarados en su struct. Esto corresponde a una secuencia de asignaciones que se ejecutan inmediatamente al entrar en el constructor.

Cuando un constructor de instancia de una estructura tiene un inicializador de constructor this() que representa el constructor predeterminado sin parámetros, este constructor declarado borra, de forma implícita, todos los campos de instancia. Además, realiza las inicializaciones especificadas por los inicializadores de variables de los campos de instancia declarados en su estructura. Inmediatamente después de la entrada al constructor, todos los campos de tipo de valor se establecen en su valor predeterminado y todos los campos de tipo de referencia se establecen en null. Inmediatamente después, se ejecuta una secuencia de asignaciones correspondientes a los variable_initializer.

Asignación definitiva

Los campos de instancia (distintos de fixed) deben asignarse definitivamente en constructores de instancia de estructura que no tengan un inicializador this() (consulte §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; }
}

Sin inicializador base()

No se permite un inicializador base() en los constructores de estructuras.

El compilador no emitirá una llamada al constructor de base System.ValueType desde los constructores de instancias de estructuras.

record struct

Se notifica un error si un record struct tiene inicializadores de campo y no contiene un constructor principal ni ningún constructor de instancia, ya que no se ejecutarán los inicializadores de campo.

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

Un record struct con una lista de parámetros vacía tendrá un constructor principal sin parámetros.

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

Un constructor sin parámetros explícito en un record struct debe tener un inicializador this que llame al constructor principal o a un constructor declarado explícitamente.

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

Fields

El constructor sin parámetros definido implícitamente pondrá a cero los campos en lugar de llamar a cualquier constructor sin parámetros para los tipos de campo. No se informa de advertencias de que se ignoran los constructores de campo. Sin cambios respecto a 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
}

Expresión default

default omite el constructor sin parámetros y genera una instancia inicializada en ceros. Sin cambios respecto a C#9.

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

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

new()

La creación de objetos invoca el constructor sin parámetros si es público; de lo contrario, la instancia es cero. Sin cambios respecto a C#9.

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

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

Una onda de advertencia puede informar de una advertencia por el uso de new() con un tipo struct que tiene constructores pero no constructor sin parámetros. No se notificará ninguna advertencia al usar la sustitución de este tipo de estructura para un parámetro de tipo con una restricción de new() o struct.

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

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

Valores no inicializados

Un campo o una variable local de un tipo de estructura que no se inicializa explícitamente se establece en cero. El compilador notifica un error de asignación definitiva para una estructura sin inicializar que no está vacía. Sin cambios respecto a 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)

Asignación de matrices

La asignación de matrices omite cualquier constructor sin parámetros y genera elementos inicializados a cero. Ningún cambio desde C#9.

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

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

Valor predeterminado del parámetro new()

Un valor predeterminado de parámetro de new() enlaza al constructor sin parámetros si es público (e informa de un error que indica que el valor no es constante); de lo contrario, la instancia es cero. Sin cambios respecto a 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

Restricciones de parámetros de tipo: new() y struct

Las restricciones de parámetros de tipo new() y struct requieren que el constructor sin parámetros debe ser public si se define (vea Cumplir con las restricciones - §8.4.5).

El compilador asume que todas las estructuras cumplen las restricciones new() y struct. Ningún cambio con respecto a 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() se emite como una llamada a System.Activator.CreateInstance<T>()y el compilador asume la implementación de CreateInstance<T>() invoca al constructor sin parámetros public si se define.

Con .NET Framework, Activator.CreateInstance<T>() invoca el constructor sin parámetros si la restricción es where T : new() pero parece omitir el constructor sin parámetros si la restricción es where T : struct.

Parámetros opcionales

Los constructores con parámetros opcionales no se consideran constructores sin parámetros. Sin cambios respecto a 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

Metadatos

Los constructores explícitos de instancias struct sin parámetros se emitirán a los metadatos.

Los constructores de instancia de struct sin parámetros públicos se importarán desde metadatos; se omitirán los constructores de instancia de struct no públicos. Sin cambios respecto a C#9.

Consulte también

Reuniones de diseño