Construtores struct sem parâmetros
Observação
Este artigo é uma especificação de recurso. A especificação serve como o documento de design para o recurso. Ela inclui alterações de especificação propostas, juntamente com as informações necessárias durante o design e o desenvolvimento do recurso. Esses artigos são publicados até que as alterações de especificação propostas sejam finalizadas e incorporadas na especificação ECMA atual.
Pode haver algumas divergências entre a especificação do recurso e a implementação concluída. Essas diferenças são capturadas nas notas pertinentes da reunião de design de idioma (LDM).
Você pode saber mais sobre o processo de adoção de especificações de funções no padrão de linguagem C# no artigo das especificações .
Problema do especialista: https://github.com/dotnet/csharplang/issues/99
Resumo
Suporte para construtores sem parâmetros e inicializadores de campos de instância em tipos de struct.
Motivação
Construtores explícitos sem parâmetros davam mais controle sobre instâncias minimamente construídas do tipo struct.
Inicializadores de campo de instância permitiam a inicialização simplificada em vários construtores.
Juntos, isso fecharia uma lacuna óbvia entre declarações struct
e class
.
O suporte para inicializadores de campo também permitia a inicialização de campos em declarações record struct
sem implementar explicitamente o construtor primário.
record struct Person(string Name)
{
public object Id { get; init; } = GetNextId();
}
Se inicializadores de campo de struct forem suportados para construtores com parâmetros, parece natural estender esse suporte também a construtores sem parâmetros.
record struct Person()
{
public string Name { get; init; }
public object Id { get; init; } = GetNextId();
}
Proposal
Inicializadores de campo de instância
As declarações de campo de instância de um struct podem incluir inicializadores.
Assim como acontece com inicializadores de campo de classe §15.5.6.3:
Um inicializador de variável para um campo de instância não pode referenciar a instância que está sendo criada.
Um erro será relatado se um struct tiver inicializadores de campo e nenhum construtor de instância declarado, pois os inicializadores de campo não serão executados.
struct S { int F = 42; } // error: 'struct' with field initializers must include an explicitly declared constructor
Construtores
Um struct pode declarar um construtor de instância sem parâmetros.
Um construtor de instância sem parâmetros é válido para todos os tipos de struct, incluindo struct
, readonly struct
, ref struct
e record struct
.
Se nenhum construtor de instância sem parâmetros for declarado, a struct (consulte §16.4.9) ...
implicitamente tem um construtor de instância sem parâmetros que sempre retorna o valor resultante da definição de todos os campos de tipo de valor para o valor padrão e todos os campos de tipo de referência como nulos.
Modificadores
Um construtor de struct de instância sem parâmetros deve ser declarado public
.
struct S0 { } // ok
struct S1 { public S1() { } } // ok
struct S2 { internal S2() { } } // error: parameterless constructor must be 'public'
Construtores não públicos são ignorados ao importar tipos de metadados.
Construtores podem ser declarados extern
ou unsafe
.
Construtores não podem ser partial
.
Execução de inicializadores de campo
Inicializadores de variável de instância (§15.11.3) são modificados da seguinte maneira:
Quando um construtor de instância de uma classe não tem nenhum inicializador de construtor ou tem um inicializador de construtor da forma
base(...)
, esse construtor executa implicitamente as inicializações especificadas pelos variable_initializers dos campos de instância declarados em sua classe. Isso corresponde a uma sequência de atribuições executadas imediatamente após a entrada no construtor e antes da invocação implícita do construtor de classe base direta.Quando um construtor de instância de struct não tem um inicializador de construtor, esse construtor executa de forma implícita as inicializações especificadas pelos variable_initializers dos campos de instância declarados em sua estrutura. Isso corresponde a uma sequência de atribuições que são executadas imediatamente após a entrada no construtor.
Quando um construtor de instância de struct tem um inicializador de construtor
this()
que representa o construtor sem parâmetro padrão, o construtor declarado limpa implicitamente todos os campos de instância e executa as inicializações especificadas pelos variable_initializers dos campos de instância declarados em seu struct. Imediatamente após a entrada no construtor, todos os campos de tipo de valor são definidos como seu valor padrão e todos os campos de tipo de referência são definidos comonull
. Imediatamente após isso, uma sequência de atribuições correspondentes aos variable_initializers são executadas.
Atribuição definida
Campos de instância (diferentes de campos fixed
) devem ser definitivamente atribuídos em construtores de instância de struct que não têm um inicializador de 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; }
}
Nenhum inicializador de base()
Um inicializador base()
não é permitido em construtores de struct.
O compilador não emitirá uma chamada para o construtor base System.ValueType
a partir dos construtores de instância do struct.
record struct
Um erro será relatado se um record struct
tiver inicializadores de campo e não contiver um construtor primário nem nenhum construtor de instância, pois os inicializadores de campo não serão executados.
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
Um record struct
com uma lista de parâmetros vazia terá um construtor primário sem parâmetros.
record struct R3(); // primary .ctor: public R3() { }
record struct R4() { int F = 42; } // primary .ctor: public R4() { F = 42; }
Um construtor sem parâmetros explícito em um record struct
deve ter um inicializador this
que chame o construtor primário ou um construtor declarado explicitamente.
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
O construtor sem parâmetros definido implicitamente definirá os campos como zero em vez de chamar construtores sem parâmetros para os tipos de campo. Nenhum aviso é relatado de que os construtores de campo são ignorados. Nenhuma alteração do 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
expressão
default
ignora o construtor sem parâmetros e gera uma instância zero.
Nenhuma alteração do C#9.
// struct S { public S() { } }
_ = default(S); // constructor ignored, no warning
new()
A criação de objeto invoca o construtor sem parâmetros se ele for público; caso contrário, a instância é zerada. Nenhuma alteração do C#9.
// public struct PublicConstructor { public PublicConstructor() { } }
// public struct PrivateConstructor { private PrivateConstructor() { } }
_ = new PublicConstructor(); // call PublicConstructor::.ctor()
_ = new PrivateConstructor(); // initobj PrivateConstructor
Uma onda de aviso pode relatar um aviso para uso de new()
com um tipo de struct que tem construtores, mas nenhum construtor sem parâmetros.
Nenhum aviso será relatado ao substituir um tipo de struct desse tipo por um parâmetro de tipo com uma restrição new()
ou 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 não inicializados
Um local ou campo de um tipo de struct que não é explicitamente inicializado é zerado. O compilador relata um erro de atribuição definido para um struct não inicializado que não está vazio. Nenhuma alteração do 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)
Alocação de matriz
A alocação de matriz ignora todo construtor sem parâmetros e gera elementos zerados. Nenhuma alteração do C#9.
// struct S { public S() { } }
var a = new S[1]; // constructor ignored, no warning
Valor de parâmetro padrão new()
Um valor padrão de parâmetro de new()
se associa ao construtor sem parâmetros se público (e indica um erro dizendo que o valor não é constante); caso contrário, a instância será zerada.
Nenhuma alteração do 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
Restrições de parâmetro de tipo: new()
e struct
As restrições de parâmetro de tipo new()
e struct
exigem que o construtor sem parâmetros seja public
se definido (consulte Atender a restrições - §8.4.5).
O compilador pressupõe que todos os structs atendam às restrições new()
e struct
.
Nenhuma alteração do 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()
é emitido como uma chamada para System.Activator.CreateInstance<T>()
e o compilador pressupõe que a implementação de CreateInstance<T>()
invoca o construtor sem parâmetros public
, se definido.
Com o .NET Framework, Activator.CreateInstance<T>()
invocará o construtor sem parâmetros se a restrição for where T : new()
, mas parece ignorar o construtor sem parâmetros se a restrição for where T : struct
.
Parâmetros opcionais
Construtores com parâmetros opcionais não são considerados construtores sem parâmetros. Nenhuma alteração do 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
Metadados
Construtores de instância de struct explícitos sem parâmetros serão emitidos para metadados.
Os construtores de instância de struct sem parâmetros públicos serão importados a partir de metadados; os construtores de instância de struct não públicos serão ignorados. Nenhuma alteração do C#9.
Confira também
Reuniões de design
- 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