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.
- 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
requiredC# 11, o erroCS9035e 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
Estoqueficou 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 é umProduto. É a mesma coisa quevar 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; chamaCompras.Add("caderno")eCompras.Add("lápis")na lista que o cliente já tem. Por isso funciona até comComprassó comget.
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; }
requiredvem antes do tipo. Quem cria umProdutoé obrigado a dar valor aoNomenas chaves. Como agora é impossível ele ficar sem valor, o= ""não é mais necessário, e o avisoCS8618desaparece.- public decimal Preco { get; init; }
- Sem
required: continua opcional. O brinde ficou comR$ 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
[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?
| Forma | Como fica | Quando usar |
|---|---|---|
| Construtor com parâmetros | new Produto("Caderno", 12.50m) | Poucos dados, todos obrigatórios, ou quando a criação precisa de validação |
| Inicializador de objeto | new Produto { Nome = "…", Preco = … } | Vários dados, alguns opcionais; fica claro o que é cada valor |
Inicializador + required | required string Nome { get; init; } | Como o anterior, mas com campos que não podem faltar |
| Construtor + inicializador | new Produto("Borracha") { Preco = 2.50m } | O essencial no construtor, os extras nas chaves |
Quando dá errado
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.
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.
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].
$ 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
| Escrita | Significado |
|---|---|
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# 9 | new 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# 11 | Obrigató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("…") { … }), comnew()e com outros inicializadores dentro dele. - As chaves respeitam o encapsulamento: nome errado é
CS0117; semset/initéCS0200. requiredC# 11 torna a propriedade obrigatória na criação (CS9035) e dispensa o= "". Cominit, fica obrigatória e fixa.- Construtor que preenche os obrigatórios precisa de
[SetsRequiredMembers].