Compartilhar via


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 structe 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 como null. 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