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. Ele inclui mudanças de especificação propostas, juntamente com as informações necessárias durante o design e desenvolvimento do recurso. Estes artigos são publicados até que as alterações de especificações propostas sejam finalizadas e incorporadas na especificação ECMA atual.
Pode haver algumas discrepâ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 linguagens (LDM) .
Você pode saber mais sobre o processo de adoção de especificações de recursos no padrão de linguagem C# no artigo sobre as especificações .
Questão campeã: https://github.com/dotnet/csharplang/issues/99
Resumo
Suporta construtores sem parâmetros e inicializadores de campo de instância para tipos struct.
Motivação
Construtores sem parâmetros explícitos dariam mais controle sobre instâncias minimamente construídas do tipo struct.
Os inicializadores de campo de instância permitiriam a inicialização simplificada em vários construtores.
No seu conjunto, estas declarações colmatariam um fosso óbvio entre struct
e class
.
O suporte para inicializadores de campo também permitiria a inicialização de campos em declarações de record struct
sem implementar explicitamente o construtor primário.
record struct Person(string Name)
{
public object Id { get; init; } = GetNextId();
}
Se inicializadores de campo struct são suportados para construtores com parâmetros, parece natural estender isso para construtores sem parâmetros também.
record struct Person()
{
public string Name { get; init; }
public object Id { get; init; } = GetNextId();
}
Proposta
Inicializadores de campo de instância
As declarações de campo de instância para uma struct podem incluir inicializadores.
Tal como acontece com os inicializadores de campo de classe §15.5.6.3:
Um inicializador de variável para um campo de instância não pode fazer referência à instância que está sendo criada.
Um erro é relatado se um struct tiver inicializadores de campo e nenhum construtor de instância declarado, uma vez que 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 struct, incluindo struct
, readonly struct
, ref struct
e record struct
.
Se nenhum construtor de instância sem parâmetros for declarado, a estrutura (ver §16.4.9) ...
implicitamente tem um construtor de instância sem parâmetros que sempre retorna o valor que resulta da definição de todos os campos de tipo de valor para seu valor padrão e todos os campos de tipo de referência para null.
Modificadores
Um construtor de estrutura 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.
Os construtores podem ser declarados extern
ou unsafe
.
Os construtores não podem ser partial
.
Executando inicializadores de campo
Inicializadores de variáveis de instância (§15.11.3) são modificados da seguinte forma:
Quando uma classe construtor de instância não tem nenhum inicializador de construtor, ou ele tem um inicializador de construtor do formulário
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 que são 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 struct não tem inicializador de construtor, esse construtor executa implicitamente as inicializações especificadas pelos variable_initializers dos campos de instância declarados em seu struct. 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 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 definitiva
Os campos de instância (diferentes de campos fixed
) devem ser definitivamente atribuídos em construtores de instância struct que não tenham um 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; }
}
Sem inicializador base()
Um inicializador base()
não é permitido em construtores de estrutura.
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 é relatado se um record struct
tiver inicializadores de campo e não contiver um construtor primário nem nenhum construtor de instância, uma vez que 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 chama o construtor primário ou um construtor explicitamente declarado.
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;
}
Campos
O construtor sem parâmetros definido implicitamente tornará os campos zero em vez de chamar quaisquer 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 zerada.
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 for público; caso contrário, a instância é inicializada com zero. 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 struct que tenha construtores, mas nenhum construtor sem parâmetros.
Nenhum aviso será emitido ao substituir tal tipo struct por um parâmetro de tipo com uma restrição de 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 struct que não é explicitamente inicializado é zerado. O compilador relata um erro de atribuição definitiva para uma struct não inicializada que não está vazia. 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 matrizes
A alocação de matriz ignora qualquer 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 padrão do parâmetro new()
Um valor padrão de parâmetro de new()
é vinculado ao construtor sem parâmetros se for público (e relata um erro de que o valor não é constante); caso contrário, a instância é inicializada a zero.
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âmetros de tipo: new()
e struct
As restrições dos parâmetros de tipo new()
e struct
exigem que o construtor sem parâmetros seja public
, se definido (consulte Satisfying constraints - , §8.4.5,).
O compilador assume que todas as structs satisfazem new()
e struct
restrições.
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 assume que a implementação de CreateInstance<T>()
invoca o construtor public
sem parâmetros, se definido.
Com o .NET Framework, Activator.CreateInstance<T>()
invoca 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 estiver 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 struct sem parâmetros explícitos serão emitidos para metadados.
Os construtores públicos de instâncias struct sem parâmetros serão importados a partir dos metadados; os construtores de instância struct não públicos serão ignorados. Nenhuma alteração do C#9.
Ver 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