1. Classe vs. objeto
Uma classe é a planta baixa; um objeto é a casa construída a partir dela. Você pode ter várias casas (objetos/instâncias) construídas a partir da mesma planta (classe), cada uma com seus próprios valores.
public class Cliente
{
public int Id { get; set; }
public string Nome { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
}
var cliente1 = new Cliente { Id = 1, Nome = "Ana" };
var cliente2 = new Cliente { Id = 2, Nome = "Bruno" };
// dois objetos independentes, a partir da mesma classe
Isso lembra um objeto literal do JS ({'{'} id: 1, nome: "Ana" {'}'})
ou uma class do ES6 — a diferença é que em C# a forma
do objeto (quais propriedades existem, de que tipo) é fixada pela classe e checada em tempo de
compilação. Não dá para criar um Cliente com uma propriedade
Idade que não foi declarada na classe.
2. Propriedades (properties) — o get/set por trás dos panos
{'{'} get; set; {'}'} é açúcar sintático para um par de métodos que
leem e escrevem um campo interno. A forma expandida ajuda a entender o que realmente acontece:
// forma curta (auto-implementada) — o mais comum
public string Nome { get; set; } = string.Empty;
// forma expandida — equivalente, com um campo privado por trás
private string _nome = string.Empty;
public string Nome
{
get { return _nome; }
set { _nome = value; }
}
// propriedade com validação — só a forma expandida permite isso
private decimal _saldo;
public decimal Saldo
{
get => _saldo;
set
{
if (value < 0)
throw new ArgumentException("Saldo não pode ser negativo");
_saldo = value;
}
}
Use a forma expandida sempre que a propriedade precisar de uma regra (validação, cálculo, notificação de mudança). Para o caso comum — só guardar um valor — a forma auto-implementada é suficiente e mais legível.
3. Construtores
Um construtor é um método especial, com o mesmo nome da classe, que roda quando um objeto é
criado com new. É o lugar certo para garantir que um objeto nasce em
um estado válido.
public class Cliente
{
public string Nome { get; }
public string Email { get; }
public Cliente(string nome, string email)
{
if (string.IsNullOrWhiteSpace(nome))
throw new ArgumentException("Nome é obrigatório", nameof(nome));
Nome = nome;
Email = email;
}
}
var cliente = new Cliente("Ana", "ana@x.com"); // só assim é possível criar um Cliente
// new Cliente() -- não compila: não existe construtor sem parâmetros
Propriedades declaradas só com get (sem set)
só podem ser atribuídas dentro do construtor — depois disso, são efetivamente somente leitura.
Esse é o caminho mais simples para criar objetos imutáveis em C#.
4. Modificadores de acesso
| Modificador | Visível para |
|---|---|
public | Qualquer código, de qualquer lugar |
private | Só dentro da própria classe (padrão se omitido) |
protected | A própria classe e classes que herdam dela (capítulo 6) |
internal | Qualquer código no mesmo projeto/assembly |
Fig. 1 — Encapsulamento: dados privados, expostos e controlados só através de métodos/propriedades públicos.
5. Encapsulamento na prática
public class ContaBancaria
{
private decimal _saldo;
public decimal Saldo => _saldo; // só leitura pública
public void Depositar(decimal valor)
{
if (valor <= 0)
throw new ArgumentException("Valor deve ser positivo");
_saldo += valor;
}
public void Sacar(decimal valor)
{
if (valor > _saldo)
throw new InvalidOperationException("Saldo insuficiente");
_saldo -= valor;
}
}
var conta = new ContaBancaria();
conta.Depositar(100);
// conta._saldo = 999999; -- não compila: _saldo é privado
Esse é o motivo de existir encapsulamento: sem ele, qualquer código poderia colocar
_saldo num valor impossível, ignorando as regras de negócio. Com
private + métodos públicos controlando o acesso, a única forma de
mudar o saldo é através de regras que você mesmo garante.
6. Métodos estáticos vs. de instância
public class Calculadora
{
public static decimal Somar(decimal a, decimal b) => a + b; // não precisa de instância
}
decimal resultado = Calculadora.Somar(2, 3); // chama direto na classe
// Console.WriteLine, que você já usou desde o capítulo 1, é exatamente isso:
// um método estático da classe Console.
7. Onde isso te leva
Com classes, objetos e encapsulamento cobertos, o próximo capítulo adiciona a segunda metade da orientação a objetos: como uma classe reaproveita e especializa o comportamento de outra (herança), e como código genérico pode trabalhar com vários tipos diferentes de forma uniforme (polimorfismo).