C# Inicializadores e required

Criar um objeto e depois preencher as propriedades uma por uma funciona, mas espalha a criação por várias linhas. O C# tem um jeito mais direto: o inicializador de objeto, que cria e preenche tudo de uma vez, com o nome de cada propriedade à vista. E, quando uma propriedade não pode ficar de fora de jeito nenhum, a palavra required ("obrigatório") faz o compilador cobrar.

Você vai aprender
  • O inicializador de objeto: new Produto { Nome = "Caderno", Preco = 12.50m }.
  • Como ele se combina com um construtor e com o new() sem o nome da classe.
  • Inicializadores dentro de inicializadores (objetos e listas aninhados).
  • O que o inicializador não pode fazer (CS0117, CS0200).
  • O modificador required C# 11, o erro CS9035 e o atributo [SetsRequiredMembers].

Esta aula continua readonly e init, onde o inicializador apareceu pela primeira vez.


A ideia: a ficha com os campos escritos

Imagine duas formas de cadastrar um produto na lojinha da Ana. Na primeira, alguém dita: "Caderno, doze e cinquenta, trinta". Quem anota precisa lembrar que o primeiro é o nome, o segundo é o preço e o terceiro é o estoque. Na segunda, a pessoa preenche uma ficha que diz "Nome: ___", "Preço: ___", "Estoque: ___". Não há como confundir, e dá para deixar um campo em branco.

O construtor com parâmetros é o ditado: new Produto("Caderno", 12.50m, 30). O inicializador de objeto é a ficha: new Produto { Nome = "Caderno", Preco = 12.50m, Estoque = 30 }.


Do jeito longo: linha por linha

var caderno = new Produto();
caderno.Nome = "Caderno";
caderno.Preco = 12.50m;
caderno.Estoque = 30;
Console.WriteLine($"{caderno.Nome}: {caderno.Preco:C} ({caderno.Estoque} no estoque)");

class Produto
{
    public string Nome { get; set; } = "";
    public decimal Preco { get; set; }
    public int Estoque { get; set; }
}
$ dotnet run linha-a-linha.cs
Caderno: R$ 12,50 (30 no estoque)

Funciona, mas o nome caderno se repete em todas as linhas, e entre o new e a última linha existe um caderno "pela metade", sem preço nem estoque.


O inicializador de objeto

var caderno = new Produto
{
    Nome = "Caderno",
    Preco = 12.50m,
    Estoque = 30,
};
var lapis = new Produto { Nome = "Lápis", Preco = 1.75m };

Console.WriteLine($"{caderno.Nome}: {caderno.Preco:C} ({caderno.Estoque} no estoque)");
Console.WriteLine($"{lapis.Nome}: {lapis.Preco:C} ({lapis.Estoque} no estoque)");

class Produto
{
    public string Nome { get; set; } = "";
    public decimal Preco { get; set; }
    public int Estoque { get; set; }
}
$ dotnet run inicializador.cs
Caderno: R$ 12,50 (30 no estoque)
Lápis: R$ 1,75 (0 no estoque)          « o estoque ficou de fora: vale 0
new Produto
Cria o objeto, como sempre. Sem parênteses: com chaves logo depois, o () é opcional (new Produto() { … } também vale).
{ Nome = "Caderno", … }
As chaves do inicializador. Cada item é Propriedade = valor, separado por vírgula. A ordem não importa.
Estoque = 30,
A vírgula depois do último item é permitida. Muita gente usa, porque fica fácil acrescentar uma linha depois.
};
O ponto e vírgula vem depois da chave: a criação inteira é uma instrução só.
new Produto { Nome = "Lápis", Preco = 1.75m }
Para poucos itens, numa linha. O Estoque ficou de fora e ficou com o valor padrão, 0.

O inicializador não é mágica: o compilador transforma o código em "crie com new Produto() e depois atribua cada propriedade", igual ao jeito longo. A diferença é que ninguém vê o objeto pela metade, e as propriedades init (da aula anterior) também aceitam valor aqui.

Missão: reescreva a criação da mochila com um inicializador de objeto, na ordem Nome, Preco, Estoque. As três linhas mochila.… = …; devem sumir. A saída continua a mesma.

var mochila = new Produto();
mochila.Nome = "Mochila";
mochila.Preco = 89.90m;
mochila.Estoque = 5;
Console.WriteLine($"{mochila.Nome}: {mochila.Preco:C} ({mochila.Estoque} no estoque)");

class Produto
{
    public string Nome { get; set; } = "";
    public decimal Preco { get; set; }
    public int Estoque { get; set; }
}
var mochila = new Produto
{
    Nome = "Mochila",
    Preco = 89.90m,
    Estoque = 5,
};
Console.WriteLine($"{mochila.Nome}: {mochila.Preco:C} ({mochila.Estoque} no estoque)");

class Produto
{
    public string Nome { get; set; } = "";
    public decimal Preco { get; set; }
    public int Estoque { get; set; }
}

Construtor e inicializador juntos

Os dois se combinam: o construtor roda primeiro, e o inicializador preenche o resto. E, quando o tipo já está escrito à esquerda, o new() pode ficar sem o nome da classe C# 9:

var borracha = new Produto("Borracha") { Preco = 2.50m };
Produto regua = new() { Nome = "Régua", Preco = 4m };
Console.WriteLine($"{borracha.Nome}: {borracha.Preco:C}");
Console.WriteLine($"{regua.Nome}: {regua.Preco:C}");

class Produto
{
    public Produto()
    {
    }

    public Produto(string nome)
    {
        Nome = nome;
    }

    public string Nome { get; set; } = "";
    public decimal Preco { get; set; }
}
$ dotnet run ctor-init.cs
Borracha: R$ 2,50
Régua: R$ 4,00
new Produto("Borracha") { Preco = 2.50m }
Chama o construtor que recebe o nome e, em seguida, preenche o preço.
Produto regua = new() { … }
O tipo já aparece à esquerda, então new() sabe que é um Produto. É a mesma coisa que var regua = new Produto { … }; escolha um estilo e mantenha.
public Produto() { }
Como a classe tem um construtor com parâmetro, o construtor vazio precisa ser escrito, senão new Produto { … } deixa de compilar (veja Construtores).

Inicializadores aninhados

Um valor dentro das chaves pode ser outro objeto criado com inicializador. E uma lista que já existe pode receber itens com chaves, sem new:

var ana = new Cliente
{
    Nome = "Ana",
    Endereco = new Endereco { Rua = "Rua das Flores", Numero = 42 },
    Compras = { "caderno", "lápis" },
};
Console.WriteLine($"{ana.Nome} mora na {ana.Endereco.Rua}, {ana.Endereco.Numero}");
Console.WriteLine($"Comprou: {string.Join(", ", ana.Compras)}");

class Cliente
{
    public string Nome { get; set; } = "";
    public Endereco Endereco { get; set; } = new Endereco();
    public List<string> Compras { get; } = new List<string>();
}

class Endereco
{
    public string Rua { get; set; } = "";
    public int Numero { get; set; }
}
$ dotnet run aninhado.cs
Ana mora na Rua das Flores, 42
Comprou: caderno, lápis
Endereco = new Endereco { … },
Um inicializador dentro do outro: cria o endereço e guarda na propriedade Endereco.
Compras = { "caderno", "lápis" },
Sem new: isso não troca a lista; chama Compras.Add("caderno") e Compras.Add("lápis") na lista que o cliente já tem. Por isso funciona até com Compras só com get.

Repare como a forma do código lembra a forma dos dados: o endereço fica "dentro" do cliente. Para criar listas soltas (new List<string> { … } ou ["a", "b"]), veja List<T>.


O que o inicializador não pode fazer

As chaves só conseguem fazer o que você conseguiria fazer por fora, numa atribuição normal. Um nome que não existe é barrado:

var caderno = new Produto { Nome = "Caderno", Precco = 12.50m };

class Produto
{
    public string Nome { get; set; } = "";
    public decimal Preco { get; set; }
}
$ dotnet run nome-errado.cs
/home/ana/cs/nome-errado.cs(1,47): error CS0117: "Produto" não contém uma definição para "Precco"

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

E uma propriedade sem set nem init também (com public decimal Preco { get; }):

$ dotnet run sem-set.cs
/home/ana/cs/sem-set.cs(1,47): error CS0200: A propriedade ou o indexador "Produto.Preco" não pode ser atribuído, pois é somente leitura

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

Com private set, o erro é o CS0272, de Encapsulamento. O inicializador respeita o encapsulamento: ele não abre nenhuma porta que a classe deixou fechada.


O problema: esquecer uma propriedade

Liberdade para deixar campos em branco é boa para o Estoque, mas péssima para o Nome. Nada impede alguém de cadastrar um produto sem nome:

var caderno = new Produto { Preco = 12.50m };
Console.WriteLine($"[{caderno.Nome}]: {caderno.Preco:C}");

class Produto
{
    public string Nome { get; init; } = "";
    public decimal Preco { get; init; }
}
$ dotnet run esqueceu.cs
[]: R$ 12,50          « um produto sem nome na prateleira

O = "" evitou o aviso CS8618 da aula anterior, mas só escondeu o problema: o nome vazio é tão errado quanto null. O que a Ana quer é que o compilador recuse a criação de um produto sem nome.


required: o campo obrigatório da ficha C# 11

var caderno = new Produto { Nome = "Caderno", Preco = 12.50m };
var brinde = new Produto { Nome = "Adesivo" };
Console.WriteLine($"{caderno.Nome}: {caderno.Preco:C}");
Console.WriteLine($"{brinde.Nome}: {brinde.Preco:C}");

class Produto
{
    public required string Nome { get; init; }
    public decimal Preco { get; init; }
}
$ dotnet run required.cs
Caderno: R$ 12,50
Adesivo: R$ 0,00
public required string Nome { get; init; }
required vem antes do tipo. Quem cria um Produto é obrigado a dar valor ao Nome nas chaves. Como agora é impossível ele ficar sem valor, o = "" não é mais necessário, e o aviso CS8618 desaparece.
public decimal Preco { get; init; }
Sem required: continua opcional. O brinde ficou com R$ 0,00.

Agora, o produto sem nome:

var caderno = new Produto { Preco = 12.50m };
Console.WriteLine(caderno.Nome);

class Produto
{
    public required string Nome { get; init; }
    public decimal Preco { get; init; }
}
$ dotnet run required-erro.cs
/home/ana/cs/required-erro.cs(1,19): error CS9035: O membro requerido 'Produto.Nome' deve ser definido no inicializador de objeto ou construtor do atributo.

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

O erro aponta o new Produto (linha 1, coluna 19) e diz qual membro faltou. Em inglês: Required member 'Produto.Nome' must be set in the object initializer or attribute constructor. O erro aparece antes de o programa rodar: o produto sem nome nunca chega à prateleira.

required também funciona com { get; set; }: aí o valor é obrigatório na criação, mas pode mudar depois. Com init, ele é obrigatório na criação e fixo para sempre, a combinação mais comum.

required e construtores

Um detalhe que surpreende: mesmo que um construtor preencha o Nome, o compilador continua cobrando nas chaves, porque ele não lê o corpo do construtor para conferir:

var caderno = new Produto("Caderno");
Console.WriteLine(caderno.Nome);

class Produto
{
    public Produto(string nome)
    {
        Nome = nome;
    }

    public required string Nome { get; init; }
}
$ dotnet run required-ctor.cs
/home/ana/cs/required-ctor.cs(1,19): error CS9035: O membro requerido 'Produto.Nome' deve ser definido no inicializador de objeto ou construtor do atributo.

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

Para dizer "este construtor cuida dos obrigatórios", marque-o com o atributo [SetsRequiredMembers] ("preenche os membros obrigatórios"). Atributos são etiquetas entre colchetes, assunto de Atributos:

using System.Diagnostics.CodeAnalysis;

var caderno = new Produto("Caderno");
var lapis = new Produto { Nome = "Lápis" };
Console.WriteLine(caderno.Nome);
Console.WriteLine(lapis.Nome);

class Produto
{
    public Produto()
    {
    }

    [SetsRequiredMembers]
    public Produto(string nome)
    {
        Nome = nome;
    }

    public required string Nome { get; init; }
}
$ dotnet run required-sets.cs
Caderno
Lápis
O atributo é uma promessa sua. Com [SetsRequiredMembers], o compilador para de cobrar e confia em você. Se o construtor esquecer de preencher o Nome, ninguém avisa. Na dúvida, escolha um estilo só: ou construtor com parâmetros, ou required com inicializador.

Qual usar?

FormaComo ficaQuando usar
Construtor com parâmetrosnew Produto("Caderno", 12.50m)Poucos dados, todos obrigatórios, ou quando a criação precisa de validação
Inicializador de objetonew Produto { Nome = "…", Preco = … }Vários dados, alguns opcionais; fica claro o que é cada valor
Inicializador + requiredrequired string Nome { get; init; }Como o anterior, mas com campos que não podem faltar
Construtor + inicializadornew Produto("Borracha") { Preco = 2.50m }O essencial no construtor, os extras nas chaves

Quando dá errado

error CS0117: "…" não contém uma definição para "…"

O que aconteceu: um nome nas chaves não é propriedade (nem campo) da classe. Quase sempre é erro de digitação (Precco) ou de maiúscula (preco).

Como resolver: escreva o nome exatamente como está na classe.

error CS0200 / CS0272 numa linha com chaves

O que aconteceu: a propriedade não tem set nem init (CS0200) ou tem private set (CS0272).

Como resolver: se a propriedade deve ser preenchida na criação, use { get; init; }. Se não, passe o valor pelo construtor.

error CS9035: O membro requerido '…' deve ser definido no inicializador de objeto…

O que aconteceu: faltou uma propriedade required nas chaves. Também acontece com um construtor que preenche a propriedade, mas não tem [SetsRequiredMembers].

Como resolver: acrescente a propriedade nas chaves (Nome = "…") ou marque o construtor com [SetsRequiredMembers].

error CS9032: O membro requerido '…' pode ser menos visível ou ter um setter menos visível…
$ dotnet run required-private.cs          « com public required string Nome { get; private set; }
/home/ana/cs/required-private.cs(4,28): error CS9032: O membro requerido 'Produto.Nome' pode ser menos visível ou ter um setter menos visível do que o tipo que o contém'Produto'.

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

O que aconteceu: o compilador obrigaria quem cria o objeto a preencher uma propriedade que ele não tem permissão para preencher.

Como resolver: use init ou set públicos com required, ou tire o required e preencha pelo construtor.


Referência

EscritaSignificado
new Produto { Nome = "…", Preco = 1m }Cria e preenche as propriedades pelo nome
new Produto("…") { Preco = 1m }Construtor primeiro, depois as chaves
Produto p = new() { … }; C# 9new sem repetir o tipo
Endereco = new Endereco { … }Inicializador dentro de inicializador
Compras = { "a", "b" }Acrescenta itens numa lista que já existe
public required string Nome { get; init; } C# 11Obrigatório nas chaves do new (CS9035)
[SetsRequiredMembers]Este construtor preenche os required (precisa de using System.Diagnostics.CodeAnalysis;)

Missão final: o aluno sem nome

Missão: o segundo aluno foi matriculado sem nome, e a saída mostra , 6º B. Torne o Nome obrigatório: escreva required e apague o = "" dele. Rode e veja o CS9035. Depois, dê ao segundo aluno o nome Bruno.

var ana = new Aluno { Nome = "Ana", Turma = "6º A" };
var novo = new Aluno { Turma = "6º B" };
Console.WriteLine($"{ana.Nome}, {ana.Turma}");
Console.WriteLine($"{novo.Nome}, {novo.Turma}");

class Aluno
{
    public string Nome { get; init; } = "";
    public string Turma { get; init; } = "";
}
var ana = new Aluno { Nome = "Ana", Turma = "6º A" };
var novo = new Aluno { Nome = "Bruno", Turma = "6º B" };
Console.WriteLine($"{ana.Nome}, {ana.Turma}");
Console.WriteLine($"{novo.Nome}, {novo.Turma}");

class Aluno
{
    public required string Nome { get; init; }
    public string Turma { get; init; } = "";
}

Exercícios

1. Obrigatório

Qual palavra obriga quem cria o objeto a dar valor a uma propriedade nas chaves?

2. A ficha preenchida

Complete para criar um Produto com Nome igual a "Cola": var p = new Produto ___; (só a parte das chaves)

3. O erro do obrigatório

Qual é o código do erro quando falta uma propriedade required nas chaves?

4. Em new Produto { Nome = "Lápis" }, a propriedade int Estoque não foi citada e não é required. Quanto ela vale?

5. O que Compras = { "caderno", "lápis" } faz dentro de um inicializador?

6. Um construtor preenche a propriedade required Nome, mas new Produto("Caderno") dá CS9035. Como resolver?


Resumo

  • O inicializador de objeto cria e preenche numa instrução só: new Produto { Nome = "…", Preco = … }. O que ficar de fora recebe o valor padrão.
  • Ele se combina com construtor (new Produto("…") { … }), com new() e com outros inicializadores dentro dele.
  • As chaves respeitam o encapsulamento: nome errado é CS0117; sem set/init é CS0200.
  • required C# 11 torna a propriedade obrigatória na criação (CS9035) e dispensa o = "". Com init, fica obrigatória e fixa.
  • Construtor que preenche os obrigatórios precisa de [SetsRequiredMembers].