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 struct
y 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 ennull
. 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
- https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-04-28.md#open-questions-in-record-and-parameterless-structs
- https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-03-10.md#parameterless-struct-constructors
- https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-01-27.md#field-initializers
C# feature specifications