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ãoTotalestá 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. Useprivate set,initou 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. RetorneIReadOnlyList<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.