1. Introdução
Dá pra programar em C# sem propriedades, mas é tipo andar de patins na Casa Branca — até dá, mas é desconfortável. Além disso, a gente já se acostumou com a sintaxe curta, sem aqueles getters e setters chatos, e quando nosso app começa a lidar com modelos “sérios” (tipo uma classe User que representa o usuário no sistema), fica importante garantir que todos os dados necessários realmente estejam lá.
Por exemplo, se a gente tem uma classe assim:
public class User
{
public string Name { get; set; }
public int Age { get; set; }
}
É fácil esquecer de inicializar as propriedades:
User user = new User(); // Name vai ser null, Age = 0
Aí, lá na frente, depois de dez telas e cem voltas na lógica, tu vai tomar aquele famoso NullReferenceException e ficar caçando o culpado por horas.
Lembrando das propriedades só de init
Sim, dá pra usar inicialização só na criação:
public string Name { get; init; }
Mas mesmo assim, ninguém obriga quem usa a classe a passar um valor se não quiser — a maioria dos construtores padrão inicializa os campos com valores padrão (null, 0 e por aí vai).
Então, como obrigar o programador (ou até tu mesmo) a não esquecer de passar o valor certo? Pra isso que inventaram o modificador required, que chegou no C# 11.
2. Propriedades required: inicialização obrigatória
O modificador required diz pro compilador ficar de olho pra garantir que uma propriedade específica do objeto seja explicitamente definida na hora da criação. Ou seja, se tu não inicializar essa propriedade ao criar o objeto — o compilador vai te barrar, e a IDE vai pintar aquela onda vermelha assustadora.
public class User
{
public required string Name { get; set; }
public int Age { get; set; }
}
Vamos tentar criar um usuário:
// Erro de compilação: propriedade Name é obrigatória!
User user1 = new User();
Ou assim:
// Erro de compilação: propriedade required Name não foi definida
User user2 = new User { Age = 18 };
Agora do jeito certo:
User user3 = new User { Name = "Hermione", Age = 18 };
Como funciona o required?
O modificador required fala pro compilador: “Depois que o construtor do objeto rodar (qualquer um!), essa propriedade aqui tem que estar definida”.
Funciona tanto pra propriedades normais quanto pras só de init:
public required string Name { get; init; }
Se tu define um construtor customizado que inicializa o valor da propriedade required, tá tudo certo também.
Esquema típico de checagem
| Cenário | Compilador feliz? |
|---|---|
| Propriedade required não definida | ❌ |
| Definida no inicializador de objeto | ✔️ |
| Definida no construtor | ✔️ |
Exemplo no nosso modelo “Cachorro”
public class Dog
{
public required string Name { get; set; }
public int Age { get; set; }
}
Dog dog = new Dog { Name = "Bobik", Age = 5 }; // Tudo certo!
Dog badDog = new Dog { Age = 2 }; // Erro! Name não foi definido
Onde o required realmente salva?
- Passando DTO entre camadas: Se tu tem uma API, precisa garantir que todos os campos necessários cheguem sempre.
- Modelos complexos com atributos obrigatórios: Tipo Product com SKU obrigatório, Order com número obrigatório.
- Em entrevistas e code review: Se tu mostra esse tipo de sintaxe, a galera vai ler teu código com respeito (e até uma invejinha).
3. Como o required funciona com construtores?
Às vezes tu escreve o construtor na mão. O que acontece se tu não inicializar a propriedade required no construtor ou no inicializador de objeto? O compilador vai reclamar.
public class Article
{
public required string Title { get; set; }
public required string Author { get; set; }
public Article()
{
// Se não inicializar Title e Author — erro de compilação!
// Pode fazer assim:
Title = "Sem título";
Author = "Desconhecido";
}
}
Se o construtor já atribui valores pras propriedades required — beleza. Se não, tu tem que inicializar esses campos no inicializador de objeto (new Article { ... }).
Especificidades de uso
- required só funciona com propriedades, não com campos.
- required não é herdado — se a propriedade base é required, mas na filha tu não coloca required, o compilador não reclama (mas é boa prática repetir o required na filha).
- required não pode ser usado em campos automáticos ou qualquer coisa que não seja property.
4. Propriedades com sintaxe de seta
Com as versões novas do C#, a galera quer cada vez mais código enxuto e expressivo. Uma das novidades mais legais pras propriedades é a sintaxe de seta (ou expression-bodied properties).
Às vezes tu só quer uma propriedade que retorna um valor sem lógica extra. Antes, tinha que escrever o getter completo com chaves:
public int Age
{
get { return birthYear > 0 ? DateTime.Now.Year - birthYear : 0; }
}
Agora dá pra escrever bem mais curto — usando a seta (=>):
public int Age => birthYear > 0 ? DateTime.Now.Year - birthYear : 0;
Esse jeito se chama propriedade com corpo de expressão (expression-bodied property). É ótimo pra cálculos simples e deixa o código bem compacto.
Exemplo usando get e set
public class Book
{
private string _title;
public string Title
{
get => _title;
set => _title = value.Trim();
}
}
Aqui o get retorna o valor do campo, e o set atribui, tirando os espaços extras dos lados.
Exemplo só com get (somente leitura):
Se a propriedade é só leitura — dá pra escrever sem chaves, só com a =>.
public class Person
{
private string name = "Mark Twain";
// Só leitura: propriedade calculada
public string Name => name.ToUpper();
}
Aqui a propriedade Name só pode ser lida — ela sempre retorna name em maiúsculo.
5. Palavra-chave field para propriedades
Antes do C# 14, se tu queria acessar o campo oculto de uma propriedade automática direto no setter ou getter (tipo pra evitar recursão ou adicionar lógica própria) — não dava. Tinha que declarar o campo na mão.
O C# 14 deixa acessar o campo oculto da propriedade automática usando a palavra-chave field:
public class Person
{
public string Name
{
get => field; // field é o campo oculto da propriedade Name
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("Nome não pode ser vazio!");
field = value; // usa field no lugar do _name explícito
}
}
}
Antes tinha que ser assim:
private string _name;
public string Name
{
get => _name;
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("Nome não pode ser vazio!");
_name = value;
}
}
Agora é uma variável a menos pra declarar. Quanto mais enxuto, melhor.
Por que às vezes é importante acessar o campo dentro da propriedade?
- Às vezes tu quer controlar onde e como o valor é guardado (tipo se tu quer devolver uma cópia do objeto, cachear resultado ou fazer lazy-loading).
- Se tu quer usar atributos ou reflection — pode precisar do campo pelo nome.
- Em alguns casos de (de)serialização ou performance tuning tu quer mais controle sobre o armazenamento do valor.
Exemplos de uso:
Validação de dados no setter
public double Grade
{
get => field;
set
{
if (value < 0 || value > 5)
throw new ArgumentOutOfRangeException("Nota deve ser de 0 a 5");
field = value;
}
}
Estatística de alteração de valor
public int StepCount
{
get => field;
set
{
if (value > field)
{
Console.WriteLine($"Uhu! Você fez {value - field} passos a mais!");
}
field = value;
}
}
Lazy Load
public string Data
{
get
{
if (field == null)
field = LoadDataFromDatabase();
return field;
}
set => field = value;
}
6. Erros comuns e pegadinhas
Erro #1: esqueceu de inicializar a propriedade required.
O compilador não deixa passar esse código e já mostra erro na hora de compilar, o que ajuda a evitar dor de cabeça na execução.
Erro #2: propriedades required não funcionam sem inicialização completa no construtor.
Se o construtor não recebe valores pra todas as propriedades obrigatórias, o compilador vai avisar que tu esqueceu de algo.
Erro #3: tentar usar required com const ou readonly.
Esses modificadores não combinam — required só pode ser usado em propriedades normais. Se tentar misturar, vai dar erro.
GO TO FULL VERSION