Properties: Características com Controle

Properties: Características com Controle

Por que expor campos públicos torna a classe frágil e como as properties resolvem isso. A aula cobre a auto-property, substituta imediata do campo público, e a property completa com validação, capaz de rejeitar um preço negativo no instante da atribuição — a classe como guardiã do próprio estado.
Linguagem C#

• • 10 min de leitura

Na aula anterior, demos características aos nossos objetos usando campos públicos — declarações como public int Idade;. Funcionou, mas há uma fragilidade nessa abordagem que precisamos corrigir, e a correção nos leva a um dos recursos mais característicos e usados do C#: as properties (propriedades). Uma property é uma forma de dar uma característica a um objeto que parece um campo quando usada, mas que oferece algo que o campo não oferece: controle sobre como aquela característica é lida e, sobretudo, modificada. Com properties, você pode impedir que uma idade receba um valor negativo, que um preço fique abaixo de zero, que um campo obrigatório fique vazio. Nesta aula, você aprenderá a usar properties no lugar de campos públicos — a maneira idiomática e segura de expor características em C#, e a que você usará em praticamente todas as suas classes daqui em diante.

A fragilidade do campo público

Vejamos primeiro, com clareza, o problema que as properties resolvem. Com um campo público, qualquer parte do programa pode atribuir qualquer valor àquela característica, sem nenhum controle — inclusive valores que não fazem sentido:

class Pessoa
{
    public int Idade;   // campo público, sem proteção
}

Pessoa p = new Pessoa();
p.Idade = -5;    // ABSURDO, mas o campo público aceita sem reclamar
p.Idade = 9999;  // também aceito, embora improvável

Nada impede p.Idade = -5, uma idade negativa que é logicamente impossível. O campo público é uma gaveta escancarada: aceita o que lhe puserem, sem julgamento. Em um programa pequeno, você pode simplesmente tomar cuidado para não fazer isso — mas em programas maiores, onde muitas partes do código manipulam o mesmo objeto, confiar que ninguém jamais atribuirá um valor inválido é uma esperança frágil. Precisamos de uma forma de controlar o que entra na característica, rejeitando valores absurdos. É isso que a property oferece.

A auto-property: o substituto imediato do campo

Comecemos pela forma mais simples de property, que é o substituto direto e recomendado do campo público, mesmo quando ainda não precisamos de controle. Chama-se auto-property, e sua sintaxe acrescenta { get; set; } à declaração:

class Pessoa
{
    public string Nome { get; set; }
    public int Idade { get; set; }
    public string Cidade { get; set; }
}

À primeira vista, isso parece quase igual ao campo, e seu uso é de fato idêntico:

Pessoa p = new Pessoa();
p.Nome = "Ana";              // usa-se como um campo: atribui normalmente
p.Idade = 30;
Console.WriteLine(p.Nome);   // lê-se como um campo: exibe "Ana"

Você atribui e lê exatamente como faria com um campo. Então qual a diferença? As palavras get e set são o segredo. Elas indicam que esta característica tem dois "acessadores": um para obter o valor (get, do inglês "obter") e outro para definir o valor (set, do inglês "definir"). Numa auto-property, esses acessadores são gerados automaticamente e se comportam como um campo simples — por isso o uso é idêntico. Mas a existência deles é o que abre a porta para o controle: quando você precisar adicionar validação, basta expandir o set, sem mudar uma linha do código que usa a property. Essa é a razão de a recomendação profissional ser clara: exponha características por auto-properties, nunca por campos públicos. Mesmo uma auto-property trivial, idêntica em uso a um campo, preserva a liberdade de adicionar controle no futuro — enquanto trocar um campo por uma property depois pode exigir mudanças em quem o usa. Adote a auto-property como sua forma padrão de dar características a objetos.

A property completa: adicionando validação

Agora chegamos ao verdadeiro poder: a property que controla o que é atribuído. Quando queremos validar valores, escrevemos a property em sua forma completa, com o get e o set explícitos, e guardamos o valor real num campo privado auxiliar (chamado campo de apoio):

class Pessoa
{
    private int _idade;   // campo privado que guarda o valor de verdade

    public int Idade
    {
        get { return _idade; }   // ao LER a Idade, devolve o valor guardado
        set                       // ao ESCREVER a Idade, este bloco roda
        {
            if (value < 0)        // 'value' é o valor que se está tentando atribuir
            {
                Console.WriteLine("Idade não pode ser negativa. Ignorando.");
            }
            else
            {
                _idade = value;   // só guarda se for válido
            }
        }
    }
}

Há elementos novos a entender. O campo _idade é private (privado): ele guarda o valor de fato, mas só pode ser acessado de dentro da própria classe — o mundo externo não o vê. Por convenção, campos privados de apoio começam com um sublinhado (_idade). A property pública Idade é a "porta de entrada" controlada para esse campo. No get, simplesmente devolvemos o valor guardado (return _idade;). No set, ganhamos o controle: a palavra especial value representa o valor que está sendo atribuído — em p.Idade = 30, dentro do set o value vale 30 —, e podemos examiná-lo antes de guardá-lo. Aqui, verificamos se value é negativo e, se for, recusamos a atribuição (exibindo um aviso e não guardando); caso contrário, guardamos. Veja o efeito:

Pessoa p = new Pessoa();
p.Idade = 30;                // válido: o set guarda 30
Console.WriteLine(p.Idade);  // 30
p.Idade = -5;                // inválido: o set recusa e avisa
Console.WriteLine(p.Idade);  // ainda 30 — o valor absurdo foi rejeitado

A property protegeu a integridade do objeto: a idade negativa foi barrada na porta, e o objeto permaneceu num estado válido. E — crucialmente — quem usa a property não percebe a diferença: continua escrevendo p.Idade = 30 como antes. O controle acontece nos bastidores, dentro do set. Essa combinação de interface simples (usa-se como campo) com controle interno (valida como método) é a essência e a beleza da property.

Uma nota sobre a mentalidade que se abre

Vale perceber o que a property inaugura, pois é uma ideia que crescerá nas próximas aulas: a de que um objeto pode proteger a si mesmo, controlando como suas características são acessadas e evitando estados inválidos. Um objeto Pessoa bem construído garante, ele próprio, que nunca terá idade negativa — não confia que quem o usa vá tomar esse cuidado. Essa capacidade de um objeto guardar suas próprias regras e proteger seu próprio estado tem um nome, encapsulamento, e é um dos pilares da orientação a objetos, ao qual dedicaremos uma aula própria mais adiante. Por ora, guarde a semente: as properties são a ferramenta com que um objeto controla suas características, e esse controle é o começo da capacidade de um objeto se autoproteger.

O campo público mostrou sua fragilidade: qualquer parte do programa pode atribuir a ele um valor sem sentido, e a classe não tem como impedir. A property resolve isso ao transformar o acesso num ponto controlado, mantendo a mesma sintaxe simples de leitura e escrita.

Na forma automática, ela substitui o campo público sem custo algum e já deixa o caminho aberto. Na forma completa, ganha a capacidade de validar antes de aceitar, rejeitando um preço negativo ou uma idade impossível no exato instante da atribuição. É a primeira aparição de uma mentalidade que muda o modo de projetar: a classe deixa de ser um depósito passivo de dados e passa a ser guardiã da própria coerência.

Fontes e leituras recomendadas

Exercícios

Exercício 1

Reescreva a classe Produto de aulas anteriores usando auto-properties ({ get; set; }) em vez de campos públicos, para Nome, Preco e Estoque. Crie um objeto, preencha e exiba as properties, confirmando que o uso é idêntico ao dos campos. Explique por que a auto-property é preferível ao campo público mesmo sem validação.

Ver resposta

✓ Resposta: Exemplo:

class Produto
{
    public string Nome { get; set; }
    public decimal Preco { get; set; }
    public int Estoque { get; set; }
}
Produto p = new Produto();
p.Nome = "Caderno"; p.Preco = 12.5m; p.Estoque = 100;
Console.WriteLine($"{p.Nome}: R$ {p.Preco}");

O uso (atribuir e ler com o ponto) é idêntico ao dos campos. A auto-property é preferível mesmo sem validação porque preserva a liberdade futura: se um dia for preciso validar, basta expandir o set da property, sem alterar o código que já a usa; trocar um campo público por uma property depois poderia exigir mudanças em quem dependia dele. A auto-property custa o mesmo em uso e protege contra o futuro.

Exercício 2

Adicione validação à property Preco da classe Produto, escrevendo-a em forma completa (com campo de apoio _preco), de modo que valores negativos sejam recusados. Teste atribuindo um preço válido e depois um negativo, confirmando que o negativo é rejeitado.

Ver resposta

✓ Resposta: Exemplo:

class Produto
{
    private decimal _preco;
    public decimal Preco
    {
        get { return _preco; }
        set
        {
            if (value < 0)
                Console.WriteLine("Preço não pode ser negativo. Ignorando.");
            else
                _preco = value;
        }
    }
}
Produto p = new Produto();
p.Preco = 19.90m;              // válido
Console.WriteLine(p.Preco);    // 19.90
p.Preco = -5m;                 // recusado
Console.WriteLine(p.Preco);    // ainda 19.90

O set examina value e só guarda no campo de apoio _preco se for não negativo, rejeitando o preço negativo e preservando o valor válido anterior.

Exercício 3

Explique, com suas palavras, o papel da palavra value dentro de um set. Em produto.Preco = 19.90m, o que value contém quando o set da property Preco é executado?

Ver resposta

✓ Resposta: Dentro de um set, value é uma variável implícita que representa o valor que está sendo atribuído à property naquela operação. Em produto.Preco = 19.90m, quando o set da property Preco roda, value contém 19.90m — é esse valor que o set pode examinar (por exemplo, verificar se é negativo) e, se aprovado, guardar no campo de apoio. O value é, portanto, o "candidato a novo valor" que o set decide aceitar ou recusar.

Exercício 4

Explique a diferença entre um campo public e um campo private. Na property completa da aula, por que o campo de apoio _idade é private, enquanto a property Idade é public? Qual é o papel de cada um?

Ver resposta

✓ Resposta: Um campo public pode ser acessado de qualquer parte do programa, de dentro ou de fora da classe; um campo private só pode ser acessado de dentro da própria classe, sendo invisível ao mundo externo. Na property completa, o campo de apoio _idade é private para que ninguém de fora o modifique diretamente, contornando a validação — o único caminho externo para alterar a idade é pela property Idade, que é public e aplica o controle no seu set. Assim, o campo privado guarda o valor com segurança, e a property pública controla o acesso a ele: a property é a porta vigiada, o campo privado é o cofre por trás dela.

Exercício 5

Sem código: a aula afirma que, ao usar uma property, "quem a usa não percebe a diferença" em relação a um campo, mesmo quando há validação por trás. Explique por que isso é uma vantagem, especialmente ao pensar em converter uma auto-property simples numa property com validação no futuro.

Ver resposta

✓ Resposta: É uma vantagem porque significa que o contrato de uso da característica permanece estável mesmo quando sua implementação interna muda. Como uma property se usa exatamente como um campo (p.Idade = 30, Console.WriteLine(p.Idade)), você pode começar com uma auto-property simples e, mais tarde, quando surgir a necessidade de validar, convertê-la numa property completa com verificação no set — e nenhum código que já usava a property precisará ser alterado, pois a forma de atribuir e ler continua a mesma. Essa estabilidade permite evoluir a robustez de uma classe sem quebrar o resto do programa, o que seria impossível com campos públicos, cuja substituição por properties poderia exigir mudanças em cascata.

Comentários

Mais em Linguagem C#

A String a Fundo: Trabalhando com Texto
A String a Fundo: Trabalhando com Texto

Tudo o que se faz com texto em C#: medir com Length, compor mensagens por…

EF Core e PostgreSQL: o Primeiro Banco Real
EF Core e PostgreSQL: o Primeiro Banco Real

O primeiro programa C# conectado a um banco de dados real: instalação do…

Classes e Campos: Criando seu Primeiro Molde
Classes e Campos: Criando seu Primeiro Molde

A classe como molde e o objeto como exemplar: como definir uma classe com seus…