Métodos de Objeto e a Palavra this

Métodos de Objeto e a Palavra this

Como dar comportamento aos objetos: métodos que operam sobre o estado do próprio exemplar, acessando seus campos diretamente. A aula cobre métodos que agem e métodos que devolvem informação sobre o objeto, e explica a palavra this e quando ela deixa de ser opcional.
Linguagem C#

• • 10 min de leitura

Até agora, nossos objetos apenas guardavam dados: uma pessoa tinha nome e idade, uma conta tinha titular e saldo. São objetos passivos, meros depósitos. Mas a promessa da orientação a objetos, lá na Aula #22, era que um objeto agrupa características e comportamentos. Chegou a hora dos comportamentos. Nesta aula, daremos aos objetos a capacidade de fazer coisas — uma conta que sabe depositar e sacar, uma pessoa que sabe se apresentar — por meio dos métodos de objeto. E, ao fazê-lo, encontraremos uma palavra especial e importante, this, que permite a um objeto referir-se a si mesmo. É quando os objetos ganham vida própria, tornando-se coisas que não só têm estado, mas agem sobre ele.

Métodos que pertencem ao objeto

Você já domina os métodos desde a Fase 3: blocos de código nomeados que executam uma tarefa. A novidade agora é que um método pode pertencer a uma classe, tornando-se um comportamento dos objetos dela. Um método definido dentro de uma classe pode acessar e modificar as características (properties e campos) do próprio objeto, encapsulando uma ação que aquele tipo de coisa sabe realizar. Veja uma classe ContaBancaria que ganha comportamentos:

class ContaBancaria
{
    public string Titular { get; set; }
    public decimal Saldo { get; set; }

    public ContaBancaria(string titular, decimal saldoInicial)
    {
        Titular = titular;
        Saldo = saldoInicial;
    }

    // MÉTODO DE OBJETO: um comportamento que a conta sabe realizar.
    public void Depositar(decimal valor)
    {
        Saldo = Saldo + valor;   // acessa e modifica a property Saldo DESTE objeto
        Console.WriteLine($"Depósito de R$ {valor}. Novo saldo: R$ {Saldo}");
    }

    public void Sacar(decimal valor)
    {
        if (valor > Saldo)
        {
            Console.WriteLine("Saldo insuficiente.");
        }
        else
        {
            Saldo = Saldo - valor;
            Console.WriteLine($"Saque de R$ {valor}. Novo saldo: R$ {Saldo}");
        }
    }
}

Os métodos Depositar e Sacar são comportamentos da conta. Repare que, dentro deles, acessamos Saldo diretamente — sem precisar dizer "o saldo de qual conta" — porque o método pertence ao objeto e opera sobre as características dele. Saldo = Saldo + valor significa "some valor ao saldo deste objeto". Note também que Sacar toma uma decisão (if) usando o estado do próprio objeto (o Saldo), recusando o saque se não houver fundos — o comportamento não é cego; ele respeita as regras da conta.

Chamamos os métodos de um objeto com o ponto, exatamente como acessamos suas características, e como já fazíamos com tarefas.Add ou nome.ToUpper:

ContaBancaria conta = new ContaBancaria("Ana", 1000m);

conta.Depositar(500m);   // Depósito de R$ 500. Novo saldo: R$ 1500
conta.Sacar(200m);       // Saque de R$ 200. Novo saldo: R$ 1300
conta.Sacar(5000m);      // Saldo insuficiente.

Cada chamada aciona o comportamento daquele objeto específico, operando sobre seu saldo. Se tivéssemos duas contas, conta1.Depositar(100m) afetaria apenas o saldo de conta1, deixando conta2 intacta — porque cada método opera sobre o objeto pelo qual foi chamado. Os objetos agora fazem, não apenas guardam.

Métodos que devolvem informação sobre o objeto

Métodos de objeto também podem devolver valores (o retorno, da Fase 3), tipicamente para informar algo calculado a partir do estado do objeto. Veja uma pessoa que sabe informar se é maior de idade e que sabe se apresentar:

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

    public Pessoa(string nome, int idade)
    {
        Nome = nome;
        Idade = idade;
    }

    // Devolve informação calculada a partir do estado do objeto:
    public bool EhMaiorDeIdade()
    {
        return Idade >= 18;
    }

    // Executa uma ação usando o estado do objeto:
    public void Apresentar()
    {
        Console.WriteLine($"Olá, meu nome é {Nome} e tenho {Idade} anos.");
    }
}

Pessoa p = new Pessoa("Ana", 30);
p.Apresentar();                       // Olá, meu nome é Ana e tenho 30 anos.
if (p.EhMaiorDeIdade())
    Console.WriteLine($"{p.Nome} pode dirigir.");

O método EhMaiorDeIdade devolve um bool calculado a partir do próprio Idade do objeto, e Apresentar executa uma ação usando Nome e Idade. Ambos operam sobre as características do objeto pelo qual são chamados. Essa combinação — características que descrevem o objeto, e métodos que agem sobre elas ou informam sobre elas — é a forma plena de um objeto: uma coisa que é de certo modo e sabe fazer certas coisas, tudo reunido numa unidade coesa. É a promessa da Aula #22, agora completamente cumprida.

A palavra this: o objeto refere-se a si mesmo

Dentro de um método de objeto, às vezes precisamos referir-nos explicitamente ao próprio objeto sobre o qual o método está operando. Para isso existe a palavra especial this (em inglês, "este"), que significa, dentro de um método, "este objeto — aquele pelo qual o método foi chamado". Um uso muito comum de this aparece nos construtores, quando o nome de um parâmetro coincide com o nome de uma característica:

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

    public Pessoa(string nome, int idade)
    {
        this.Nome = nome;   // 'this.Nome' é a property do objeto; 'nome' é o parâmetro
        this.Idade = idade;
    }
}

Aqui, o parâmetro se chama nome (minúsculo) e a property se chama Nome (maiúsculo), então a distinção já existiria pela convenção de maiúsculas. Mas this.Nome deixa explícito que estamos falando da característica deste objeto, distinguindo-a inequivocamente do parâmetro. O this é especialmente necessário quando o parâmetro e a característica têm exatamente o mesmo nome, situação em que, sem ele, haveria ambiguidade:

public Pessoa(string Nome, int Idade)   // parâmetros com o mesmo nome das properties
{
    this.Nome = Nome;   // this.Nome = a property; Nome (sem this) = o parâmetro
    this.Idade = Idade;
}

Nesse caso, this.Nome refere-se à property do objeto, enquanto Nome (sem this) refere-se ao parâmetro; sem o this, o C# não saberia distingui-los. Na maior parte do tempo, graças à convenção de maiúsculas para properties e minúsculas para parâmetros, você não precisa do this — mas é importante reconhecê-lo, porque você o verá com frequência em código de outras pessoas, e ele torna explícito, em qualquer situação, que uma característica pertence ao próprio objeto. Pense em this como a forma de o objeto dizer "eu mesmo".

As classes deixaram de ser apenas conjuntos de dados. Um método de objeto opera sobre o estado daquele exemplar específico, acessando seus próprios campos sem precisar recebê-los como parâmetro — é essa proximidade entre dado e comportamento que dá sentido à ideia de objeto.

A palavra this tornou explícito o mecanismo por trás disso: dentro de um método, ela designa o objeto sobre o qual o método foi chamado. Na maior parte do tempo ela é opcional, porque o C# a subentende; ela se torna necessária quando um parâmetro tem o mesmo nome de um campo e é preciso distinguir um do outro sem ambiguidade.

Fontes e leituras recomendadas

Exercícios

Exercício 1

Adicione à classe Produto (com properties Nome, Preco, Estoque) dois métodos de objeto: Vender(int quantidade), que reduz o estoque (recusando se não houver estoque suficiente), e Repor(int quantidade), que aumenta o estoque. Crie um produto e teste os dois comportamentos.

Ver resposta

✓ Resposta: Exemplo:

class Produto
{
    public string Nome { get; set; }
    public int Estoque { get; set; }
    public Produto(string nome, int estoque) { Nome = nome; Estoque = estoque; }

    public void Vender(int quantidade)
    {
        if (quantidade > Estoque)
            Console.WriteLine("Estoque insuficiente.");
        else
        {
            Estoque = Estoque - quantidade;
            Console.WriteLine($"Vendidos {quantidade}. Estoque: {Estoque}");
        }
    }
    public void Repor(int quantidade)
    {
        Estoque = Estoque + quantidade;
        Console.WriteLine($"Repostos {quantidade}. Estoque: {Estoque}");
    }
}

Vender reduz o estoque (com verificação de suficiência) e Repor o aumenta, ambos operando sobre o Estoque do próprio objeto.

Exercício 2

Crie uma classe Contador com uma property Valor (iniciada em 0 pelo construtor) e três métodos: Incrementar(), Decrementar() e Zerar(), que modificam o Valor do objeto. Crie um contador, chame os métodos em sequência, e exiba o valor após cada chamada.

Ver resposta

✓ Resposta: Exemplo:

class Contador
{
    public int Valor { get; set; }
    public Contador() { Valor = 0; }
    public void Incrementar() { Valor = Valor + 1; }
    public void Decrementar() { Valor = Valor - 1; }
    public void Zerar() { Valor = 0; }
}
Contador c = new Contador();
c.Incrementar(); Console.WriteLine(c.Valor);   // 1
c.Incrementar(); Console.WriteLine(c.Valor);   // 2
c.Decrementar(); Console.WriteLine(c.Valor);   // 1
c.Zerar();       Console.WriteLine(c.Valor);   // 0

Cada método modifica o Valor do objeto, e as chamadas em sequência alteram o estado passo a passo.

Exercício 3

Adicione à classe Pessoa um método int AnosAte(int anoFuturo) que devolva quantos anos a pessoa terá em determinado ano futuro (considerando sua idade atual e um ano de referência). Explique como o método usa o estado do próprio objeto para calcular o resultado.

Ver resposta

✓ Resposta: Exemplo:

public int AnosAte(int anoFuturo)
{
    int anoAtual = 2026;
    return Idade + (anoFuturo - anoAtual);
}

O método usa o estado do próprio objeto — sua property Idade — como base do cálculo: soma à idade atual a quantidade de anos entre o ano de referência e o ano futuro. Como Idade pertence ao objeto pelo qual o método foi chamado, o resultado é específico daquela pessoa; chamado em pessoas de idades diferentes, produz resultados diferentes, pois cada uma tem seu próprio Idade.

Exercício 4

Explique, com suas palavras, o significado da palavra this dentro de um método de objeto. Escreva um construtor de uma classe qualquer em que o this seja necessário (parâmetros com o mesmo nome das properties) e explique o que cada lado da atribuição representa.

Ver resposta

✓ Resposta: Dentro de um método de objeto, this significa "este objeto" — o objeto específico pelo qual o método foi chamado —, permitindo referir-se explicitamente às características do próprio objeto. Exemplo em que é necessário:

public Pessoa(string Nome, int Idade)
{
    this.Nome = Nome;     // this.Nome = a property do objeto; Nome = o parâmetro
    this.Idade = Idade;
}

Aqui os parâmetros têm o mesmo nome das properties. Do lado esquerdo, this.Nome representa a property do objeto (a característica que estamos preenchendo); do lado direito, Nome (sem this) representa o parâmetro (o valor recebido). O this é necessário porque, sem ele, os dois nomes idênticos seriam ambíguos, e o C# não saberia qual é a property e qual é o parâmetro.

Exercício 5

Sem código: um método de objeto como conta.Depositar(500m) opera sobre o saldo daquela conta específica. Explique como o objeto "sabe" sobre qual saldo operar, e por que chamar conta1.Depositar(100m) não afeta o saldo de conta2. Relacione com a ideia de que cada objeto tem suas próprias características.

Ver resposta

✓ Resposta: O objeto "sabe" sobre qual saldo operar porque o método é chamado através de um objeto específico (conta.Depositar(500m)), e, dentro do método, as referências às características (como Saldo) apontam para as características daquele objeto — o que foi usado antes do ponto. Assim, conta1.Depositar(100m) opera sobre o Saldo de conta1, e conta2.Depositar(...) operaria sobre o de conta2, porque cada objeto tem seu próprio conjunto de características, independentes das de outros objetos. Chamar o método em conta1 não afeta conta2 porque são objetos distintos, cada um com seu próprio saldo; o método sempre age sobre o estado do objeto pelo qual foi acionado, e nunca sobre o de outro.

Comentários

Mais em Linguagem C#

IEnumerable: O que Torna Algo "Percorrível"
IEnumerable: O que Torna Algo "Percorrível"

Por que arrays, listas, dicionários e conjuntos aceitam todos o mesmo foreach…

A Instrução switch: Escolhendo Entre Muitas Opções
A Instrução switch: Escolhendo Entre Muitas Opções

Quando comparar um mesmo valor contra muitas opções, encadear condições fica…

Conversão de Tipos: Transformando Texto em Número, com Segurança
Conversão de Tipos: Transformando Texto em Número, com Segurança

Como transformar o texto que o usuário digita em números utilizáveis. A…