Menu

Propriedades em C#: get, set, auto-properties, init e required

Como as propriedades funcionam em C#: acessores get e set sobre um campo de apoio, auto-properties, private set e propriedades só com get, propriedades calculadas, validação nos setters e as palavras-chave init e required do C# 9 e 11.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Uma propriedade parece um campo para o código que a usa (order.Total, order.Total = 5), mas na verdade é um par de métodos: um acessor get que retorna um valor e um acessor set que recebe um. Essa indireção permite que uma classe verifique valores, calcule-os ou recuse escritas, sem mudar a forma como quem chama os usa.

Campos vs propriedades

Um campo público dá acesso direto ao armazenamento do objeto. Qualquer um pode colocar qualquer coisa nele:

public class Account
{
    public decimal Balance;   // any code can write -500 here
}

Uma propriedade coloca código entre quem chama e o armazenamento. Aqui está a forma completa, escrita à mão: um campo de apoio privado mais uma propriedade com os dois acessores. Dentro do set, a palavra-chave value é o valor sendo atribuído.

Saída:

250
Rejected a negative balance
250

Além da validação, as propriedades importam porque a maior parte do ecossistema .NET trabalha com elas e ignora campos: interfaces podem declarar propriedades, mas não campos de instância, o data binding no WPF e no MAUI se liga a propriedades, e o System.Text.Json serializa propriedades públicas por padrão e ignora campos.

Auto-properties

Quando os acessores só leriam e escreveriam o campo, deixe o compilador escrevê-los. { get; set; } é uma propriedade autoimplementada: o compilador gera o campo de apoio oculto para você.

Saída:

Notebook: 9.99, 10 left

Parece um campo, então para que se dar ao trabalho? Porque transformar depois um campo público em propriedade é uma mudança que quebra o código compilado que o usa (e argumentos ref e out), enquanto transformar uma auto-property em uma propriedade completa com validação é invisível para quem chama. Começar com { get; set; } mantém essa opção aberta de graça.

private set e propriedades só com get

A maior parte do estado deve poder ser lida por todos e alterada só pelo próprio objeto. Dê ao setter um modificador de acesso mais restrito:

Saída:

Ines: 2 items, total 15.75

Três tipos de somente leitura aqui:

  • { get; private set; }: a classe pode mudá-la a qualquer momento, o código de fora não.
  • { get; } (auto-property só com get, C# 6): só pode ser atribuída em um construtor ou em um inicializador. Depois da construção, ninguém pode mudá-la, nem a própria classe. É assim que se faz uma propriedade imutável.
  • => expression (propriedade calculada): nenhum armazenamento. A expressão executa a cada leitura, então Total está sempre em dia com a lista.

Uma propriedade calculada deve ser barata e sem efeitos colaterais, porque quem chama lê propriedades sem cerimônia, em laços e no depurador. Se obter o valor dá trabalho de verdade (uma consulta ao banco, um cálculo grande), faça dela um método como CalculateTotal(), para que o custo fique visível.

Acessores com corpo de expressão

Propriedades completas com acessores de uma linha podem usar => em cada acessor:

private string title;

public string Title
{
    get => title;
    set => title = value?.Trim() ?? "";
}

public decimal Total => SumPrices(); é a forma abreviada de uma propriedade só com getter; get => ... é a mesma ideia dentro de uma propriedade que também tem setter.

Lógica no getter e no setter: notificações e valores preguiçosos

Como os acessores são métodos, eles podem fazer mais que guardar um valor. Dois padrões comuns: disparar uma notificação de mudança no setter, e calcular um valor na primeira leitura e guardá-lo em cache.

Saída:

(building summary)
Theme: light
Theme: light
Theme changed
(building summary)
Theme: dark

Changed é um evento, e o padrão do setter é como se parecem as implementações de INotifyPropertyChanged no WPF e no MAUI. nameof(Theme) mantém a string sincronizada se a propriedade for renomeada.

O erro da recursão infinita

O bug de propriedade mais comum é um setter que atribui à propriedade em vez do campo de apoio:

public string Name
{
    get { return Name; }        // calls get again, forever
    set { Name = value; }       // calls set again, forever
}

Cada acessor chama a si mesmo, e o programa morre com uma StackOverflowException, que não pode ser capturada. A solução é ler e escrever um campo separado (name, em minúsculas), ou usar uma auto-property. O C# 14 adiciona a palavra-chave field exatamente para esse caso: dentro de um acessor, field se refere ao campo de apoio gerado pelo compilador, então set => field = value.Trim(); funciona sem declarar um.

Acessores init (C# 9)

Uma propriedade só com get obriga você a passar todos os valores por um construtor. O C# 9 adicionou o init, um setter permitido só durante a criação do objeto: em um construtor ou em um inicializador de objeto.

public class Product
{
    public string Sku { get; init; }
    public decimal Price { get; init; }
}

var p = new Product { Sku = "MUG-01", Price = 8.50m };   // fine: during creation
p.Price = 4m;   // error CS8852: Init-only property or indexer 'Product.Price' can only be assigned
                // in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor

O resultado é um objeto imutável com sintaxe de inicializador, exatamente o que as record classes usam nas suas propriedades posicionais. Para "mudar" um objeto assim, crie um novo; os records fazem isso em uma linha com with.

No C# 7, o equivalente mais próximo é uma propriedade só com get mais um parâmetro de construtor:

public class Product
{
    public string Sku { get; }
    public decimal Price { get; }
    public Product(string sku, decimal price) { Sku = sku; Price = price; }
}

Membros required (C# 11)

Um inicializador de objeto não consegue obrigar quem chama a definir nada: new Product { } compila mesmo quando Sku nunca pode ficar vazio. O C# 11 adicionou o modificador required:

public class User
{
    public required string Email { get; init; }
    public string DisplayName { get; init; } = "";
}

var ok = new User { Email = "ana@example.com" };
var bad = new User { DisplayName = "Ana" };   // error CS9035: Required member 'User.Email'
                                              // must be set in the object initializer or attribute constructor.

required funciona com set e init. Um construtor que define ele mesmo os membros obrigatórios pode dizer isso com o atributo [SetsRequiredMembers], para que quem usa esse construtor não precise defini-los de novo.

Erros comuns

  • Recursão em um acessor. set { Name = value; } chama a si mesmo. Use um campo de apoio ou uma auto-property.
  • Getters caros ou com efeitos colaterais. Quem lê espera que a leitura de uma propriedade seja rápida e segura de repetir. Use um método para trabalho de verdade.
  • Campos públicos "por enquanto". Converter um campo em propriedade depois quebra a compatibilidade binária, e os serializadores ignoram campos por padrão. Comece com uma auto-property.
  • Setters públicos em tudo. { get; set; } em um saldo ou em um status convida a estados inválidos. Use private set, init ou só get, e mude o estado por meio de métodos.
  • Retornar uma coleção mutável de uma propriedade só com get. public List<string> Tags { get; } impede que quem chama substitua a lista, não que adicione itens a ela. Retorne IReadOnlyList<string> quando a coleção não deve mudar de fora.

Perguntas frequentes

O que { get; set; } significa em C#?

Declara uma propriedade autoimplementada: o compilador cria um campo privado oculto, um acessor get que o retorna e um acessor set que o atribui. public string Name { get; set; } se comporta como um campo público para quem usa, mas é um par de métodos, então você pode depois adicionar validação ou mudá-lo para private set sem mudar o código que o usa.

Qual a diferença entre um campo e uma propriedade em C#?

Um campo é uma variável guardada no objeto. Uma propriedade é um par de métodos (get e set) que parece um campo visto de fora. As propriedades permitem que uma classe valide valores, calcule resultados ou restrinja a escrita, e é com elas que trabalham o data binding, os serializadores e as interfaces. Dados públicos devem ser expostos como propriedades; os campos normalmente ficam privados.

O que é private set em C#?

public int Stock { get; private set; } deixa qualquer código ler a propriedade, mas só o código dentro da classe alterá-la. É assim que uma classe expõe um estado que é dela, como um saldo ou uma contagem, mantendo todas as mudanças atrás de métodos que aplicam as regras.

O que o init faz em C#?

init (C# 9) é um setter que só funciona enquanto o objeto está sendo criado: em um construtor ou em um inicializador de objeto. public string Sku { get; init; } permite new Product { Sku = "A-1" }, mas rejeita product.Sku = "B-2" depois, com o erro CS8852. Ele dá a objetos imutáveis a sintaxe de inicializador.

O que é uma propriedade required em C#?

Uma propriedade marcada como required (C# 11) precisa ser definida por todo inicializador de objeto que cria o tipo; deixá-la de fora é um erro de compilação (CS9035). Ela combina bem com init: public required string Email { get; init; } precisa ser informada na criação e não pode mudar depois.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR