C# this

Dentro de um método de uma classe, você escreveu Estoque = Estoque - 1; e o C# sabia que era o estoque da maçã quando a chamada era maca.Vender(). Mas como o método fala do "objeto que me chamou" quando precisa dele inteiro, ou quando um parâmetro tem o mesmo nome de um campo? Para isso existe a palavra this ("este", em inglês): dentro da classe, this é o objeto que está executando o método agora.

Você vai aprender
  • O que this é, e por que ele já estava "escondido" em Estoque = Estoque - 1;.
  • O problema da sombra: nome = nome; não faz o que parece (aviso CS1717), e o conserto this.nome = nome;.
  • Por que o costume do C# (_nome e Nome) quase sempre dispensa o this.
  • Como passar o próprio objeto para outro método: turma.Matricular(this).
  • Como devolver this para encadear chamadas: pedido.Adicionar("pão").Adicionar("leite").
  • Os outros usos da palavra (: this(…), indexadores, extensões) e os erros CS0023 e CS0026.

Todos os programas desta aula rodaram com dotnet run arquivo.cs na máquina fictícia da Ana (usuária ana, computador notebook), com o .NET 10 SDK, na pasta ~/cs.


A ideia antes do código: "eu"

Quando a Bia preenche um formulário que diz "escreva seu nome", ela escreve "Bia". Quando o Caio preenche o mesmo formulário, escreve "Caio". O formulário é um só (a classe), mas a palavra "seu" muda de dono conforme quem está preenchendo.

O this é o "eu" do objeto. O código do método é o mesmo para todos os produtos; quando a maçã executa o método, this é a maçã; quando o kiwi executa, this é o kiwi.


O this que já estava lá

Lembra do método Vender() da aula Campos? Estas duas versões são exatamente iguais para o compilador:

public void Vender()
{
    Estoque = Estoque - 1;
}

public void Vender()
{
    this.Estoque = this.Estoque - 1;
}

Quando você escreve só Estoque dentro da classe, o C# entende this.Estoque: "o estoque deste objeto". O this fica subentendido. Em maca.Vender(), o objeto antes do ponto (maca) vira o this lá dentro.

Dá para provar que this e a variável de fora são o mesmo objeto. Este método devolve o próprio this, e o ReferenceEquals ("referências iguais?") responde se duas variáveis apontam para o mesmo objeto (veja referências):

Produto maca = new Produto();
Console.WriteLine(ReferenceEquals(maca, maca.EuMesmo()));

class Produto
{
    public Produto EuMesmo()
    {
        return this;
    }
}
$ dotnet run t7.cs
True               « o this dentro do método é a própria maçã

O problema da sombra: nome = nome;

Algumas equipes chamam os campos privados de nome, sem sublinhado. Aí, no construtor, o parâmetro e o campo ficam com o mesmo nome. Veja o que acontece:

Produto p = new Produto("maçã", 7.90m);
p.Mostrar();

class Produto
{
    private string nome = "";
    private decimal preco;

    public Produto(string nome, decimal preco)
    {
        nome = nome;
        preco = preco;
    }

    public void Mostrar()
    {
        Console.WriteLine($"[{nome}]: {preco:C}");
    }
}
$ dotnet run t1.cs
/home/ana/cs/t1.cs(11,9): warning CS1717: Atribuição feita à mesma variável. Você pretendia atribuir outro elemento?
/home/ana/cs/t1.cs(12,9): warning CS1717: Atribuição feita à mesma variável. Você pretendia atribuir outro elemento?
/home/ana/cs/t1.cs(7,21): warning CS0649: Campo "Produto.preco" nunca é atribuído e sempre terá seu valor padrão 0
[]: R$ 0,00

A maçã nasceu sem nome e de graça. Dentro do construtor, o nome nome se refere ao parâmetro, não ao campo: o parâmetro, que está mais perto, faz sombra no campo (veja Escopo). Então nome = nome; copia o parâmetro para ele mesmo, e o campo nunca é tocado.

warning CS1717
"Atribuição feita à mesma variável. Você pretendia atribuir outro elemento?" O compilador percebeu que nome = nome; não faz nada. Em inglês: Assignment made to same variable; did you mean to assign something else?.
warning CS0649
Confirma: o campo preco nunca recebeu valor. (O nome não aparece aqui só porque tem o valor inicial "".)
[]: R$ 0,00
Os campos ficaram com os valores iniciais.

O conserto: this.nome = nome;

Produto p = new Produto("maçã", 7.90m);
p.Mostrar();

class Produto
{
    private string nome = "";
    private decimal preco;

    public Produto(string nome, decimal preco)
    {
        this.nome = nome;
        this.preco = preco;
    }

    public void Mostrar()
    {
        Console.WriteLine($"[{nome}]: {preco:C}");
    }
}
$ dotnet run t2.cs
[maçã]: R$ 7,90
this.nome
"O campo nome deste objeto". O this. pula a sombra e vai direto ao campo.
= nome;
Sem this, é o parâmetro: o valor que chegou no new Produto("maçã", …).
{nome} no Mostrar()
Aqui não há parâmetro com esse nome, então não há sombra: nome já é o campo, e o this. é opcional.
Por que o C# quase nunca precisa disso

As convenções do C# foram pensadas para que os nomes não colidam: campos privados com _ (_nome = nome;) e propriedades em PascalCase (Nome = nome;). Foi o que você fez em Construtores. O this.nome = nome; aparece em equipes que preferem campos sem sublinhado, e em código traduzido de Java, onde ele é o padrão. Os dois estilos estão certos; o importante é ser consistente no projeto.

A mesma armadilha com propriedades

Se o parâmetro tiver o nome da propriedade, com a mesma maiúscula (public Produto(string Nome) { Nome = Nome; }), a sombra volta: o compilador dá o CS1717 e também o CS8618, porque a propriedade Nome nunca foi preenchida. Parâmetros se escrevem em camelCase (nome) justamente para evitar isso.

Missão: rode e leia os avisos CS1717: o construtor não guarda nada, e a saída sai em branco. Conserte o construtor com this. (sem mudar os nomes dos campos nem dos parâmetros), para que a saída seja Bia - turma 2A, sem nenhum aviso.

Aluno a = new Aluno("Bia", "2A");
a.Mostrar();

class Aluno
{
    private string nome = "";
    private string turma = "";

    public Aluno(string nome, string turma)
    {
        nome = nome;
        turma = turma;
    }

    public void Mostrar()
    {
        Console.WriteLine($"{nome} - turma {turma}");
    }
}
Aluno a = new Aluno("Bia", "2A");
a.Mostrar();

class Aluno
{
    private string nome = "";
    private string turma = "";

    public Aluno(string nome, string turma)
    {
        this.nome = nome;
        this.turma = turma;
    }

    public void Mostrar()
    {
        Console.WriteLine($"{nome} - turma {turma}");
    }
}

Passar o próprio objeto: this como argumento

Às vezes, um objeto precisa se apresentar a outro: "estou aqui, sou eu". O aluno entra na turma; a turma precisa guardar aquele aluno. Dentro do método do aluno, o jeito de dizer "eu" é this:

Turma turma = new Turma();
Aluno bia = new Aluno("Bia");
Aluno caio = new Aluno("Caio");
bia.EntrarNa(turma);
caio.EntrarNa(turma);
turma.Chamada();

class Aluno
{
    public string Nome { get; }

    public Aluno(string nome)
    {
        Nome = nome;
    }

    public void EntrarNa(Turma turma)
    {
        turma.Matricular(this);
    }
}

class Turma
{
    private Aluno[] _alunos = new Aluno[30];
    private int _total;

    public void Matricular(Aluno aluno)
    {
        _alunos[_total] = aluno;
        _total++;
        Console.WriteLine($"{aluno.Nome} entrou na turma.");
    }

    public void Chamada()
    {
        for (int i = 0; i < _total; i++)
            Console.WriteLine($"{i + 1}. {_alunos[i].Nome}");
    }
}
$ dotnet run t4.cs
Bia entrou na turma.
Caio entrou na turma.
1. Bia
2. Caio
bia.EntrarNa(turma);
A Bia recebe o pedido "entre nesta turma". Dentro de EntrarNa, this é a Bia.
turma.Matricular(this);
A Bia pede à turma: "matricule a mim". O this vai como argumento, e do outro lado chega no parâmetro aluno. Na chamada do Caio, o mesmo código manda o Caio.
_alunos[_total] = aluno;
A turma guarda a referência do aluno no array. Não é uma cópia: é a própria Bia. As duas classes conversam pelos objetos.
duas classes no fim do arquivo
A ordem entre elas não importa: Aluno usa Turma, que vem depois, sem problema. Só as instruções de nível superior precisam vir antes de todas (veja CS8803).

Devolver this: chamadas encadeadas

Se um método devolve this, quem chamou recebe o mesmo objeto de volta, e pode chamar outro método dele logo em seguida, na mesma linha. É o estilo "encadeado" (ou fluente), muito usado em C#: o StringBuilder funciona assim, e o LINQ usa o mesmo jeito de encadear.

Pedido pedido = new Pedido();
pedido.Adicionar("pão").Adicionar("leite").Adicionar("café");
pedido.Mostrar();

class Pedido
{
    private string _itens = "";
    private int _quantidade;

    public Pedido Adicionar(string item)
    {
        _itens += (_quantidade > 0 ? ", " : "") + item;
        _quantidade++;
        return this;
    }

    public void Mostrar()
    {
        Console.WriteLine($"{_quantidade} itens: {_itens}");
    }
}
$ dotnet run t3.cs
3 itens: pão, leite, café
public Pedido Adicionar(string item)
O tipo de retorno é a própria classe, Pedido.
(_quantidade > 0 ? ", " : "")
O ternário põe uma vírgula antes de todo item, menos o primeiro.
return this;
Devolve o próprio pedido.
pedido.Adicionar("pão").Adicionar("leite")
Lê-se da esquerda para a direita: pedido.Adicionar("pão") devolve o pedido; no resultado, chama-se .Adicionar("leite"); e assim por diante. É o mesmo que três linhas pedido.Adicionar(…); separadas.

Outros lugares onde aparece a palavra this

EscritaSignificadoAula
this.campo, this.Metodo()Um membro deste objetoesta
Metodo(this), return this;O objeto inteiro, como valoresta
public C(…) : this(…)Um construtor chamando outro da mesma classeConstrutores
public T this[int i] { get; set; }Um indexador: o objeto se usa com [ ], como um arrayIndexadores
static … Metodo(this string texto)Um método de extensão: "acrescenta" um método a um tipo que já existeMétodos de Extensão

São usos diferentes da mesma palavra. Nesta aula, o importante são os dois primeiros: this como "o objeto atual".


Quando dá errado

CS1717: "Atribuição feita à mesma variável"
$ dotnet run t1.cs
/home/ana/cs/t1.cs(11,9): warning CS1717: Atribuição feita à mesma variável. Você pretendia atribuir outro elemento?
/home/ana/cs/t1.cs(12,9): warning CS1717: Atribuição feita à mesma variável. Você pretendia atribuir outro elemento?
/home/ana/cs/t1.cs(7,21): warning CS0649: Campo "Produto.preco" nunca é atribuído e sempre terá seu valor padrão 0
[]: R$ 0,00

O que aconteceu: nome = nome; com um parâmetro que tem o nome do campo. O parâmetro faz sombra, e a linha copia o parâmetro para ele mesmo. Compila, roda e está errado.

Como resolver: this.nome = nome;. Ou adote o costume do C#: campo _nome, e então _nome = nome;.

CS0023: encadear num método void
$ dotnet run carrinho.cs
/home/ana/cs/carrinho.cs(2,26): error CS0023: O operador "." não pode ser aplicado ao operando do tipo "void"

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

O que aconteceu: carrinho.Adicionar(6.50m).Adicionar(4.90m), com Adicionar devolvendo void ("nada"). O primeiro Adicionar não devolve objeto nenhum, então não há onde pôr o segundo ponto (a coluna 26 é esse ponto).

Como resolver: faça o método devolver o objeto (public Carrinho Adicionar(…) com return this;), ou chame um por linha.

CS0026: this onde não há objeto
Console.WriteLine(Calculadora.Dobro(4));

class Calculadora
{
    public int Base = 2;

    public static int Dobro(int x)
    {
        return x * this.Base;
    }
}
$ dotnet run t6.cs
/home/ana/cs/t6.cs(9,20): error CS0026: A palavra-chave 'this' não é válida em uma propriedade, um método ou um inicializador de campo estático

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

O que aconteceu: um método static pertence à classe, não a um objeto. Ele é chamado como Calculadora.Dobro(4), sem nenhum objeto antes do ponto, então não existe "este objeto" lá dentro. (O static tem aula própria: static.)

Como resolver: tire o static do método e chame-o num objeto (new Calculadora().Dobro(4)), ou não use membros do objeto num método static.

O mesmo erro aparece se você escrever Console.WriteLine(this); nas instruções de nível superior: elas rodam, por dentro, num método static (o Program.<Main>$ que aparece nas exceções; veja Program.cs e o Main).

$ dotnet run t5.cs
/home/ana/cs/t5.cs(1,19): error CS0026: A palavra-chave 'this' não é válida em uma propriedade, um método ou um inicializador de campo estático

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
Parâmetro com o nome da propriedade
$ dotnet run t8.cs
/home/ana/cs/t8.cs(8,12): warning CS8618: O propriedade não anulável 'Nome' precisa conter um valor não nulo ao sair do construtor. Considere adicionar o modificador "obrigatório" ou declarar o propriedade como anulável.
/home/ana/cs/t8.cs(10,9): warning CS1717: Atribuição feita à mesma variável. Você pretendia atribuir outro elemento?
[]

O que aconteceu: o construtor era public Produto(string Nome) { Nome = Nome; }, com o parâmetro em PascalCase. A propriedade ficou com null.

Como resolver: parâmetro em camelCase: public Produto(string nome) { Nome = nome; }. Ou this.Nome = Nome;, mas o primeiro é o que todo programador C# espera ler.


Referência

EscritaSignificado
thisDentro de um método, propriedade ou construtor: o objeto que está executando
Estoque (sem nada)O mesmo que this.Estoque, se não houver sombra
this.nome = nome;Campo nome deste objeto recebe o parâmetro nome
outro.Metodo(this)Passa este objeto como argumento
return this;Devolve este objeto, para encadear chamadas
ReferenceEquals(a, b)True se a e b são o mesmo objeto
this em static ou nas instruções de nível superiorErro CS0026: não há objeto

Missão final: o carrinho encadeado

Missão: a Ana quer adicionar os produtos ao carrinho numa linha só, encadeando as chamadas, mas o programa não compila: o Adicionar devolve void (rode e leia o erro). Mude o Adicionar para devolver o próprio carrinho (tipo de retorno Carrinho e return this;). A saída deve ser 3 itens, total R$ 15,40.

Carrinho carrinho = new Carrinho();
carrinho.Adicionar(6.50m).Adicionar(4.90m).Adicionar(4.00m);
carrinho.Mostrar();

class Carrinho
{
    private decimal _total;
    private int _itens;

    public void Adicionar(decimal preco)
    {
        _total += preco;
        _itens++;
    }

    public void Mostrar()
    {
        Console.WriteLine($"{_itens} itens, total {_total:C}");
    }
}
Carrinho carrinho = new Carrinho();
carrinho.Adicionar(6.50m).Adicionar(4.90m).Adicionar(4.00m);
carrinho.Mostrar();

class Carrinho
{
    private decimal _total;
    private int _itens;

    public Carrinho Adicionar(decimal preco)
    {
        _total += preco;
        _itens++;
        return this;
    }

    public void Mostrar()
    {
        Console.WriteLine($"{_itens} itens, total {_total:C}");
    }
}

Exercícios

1. Pular a sombra

No construtor public Conta(string titular), com um campo privado também chamado titular, complete a linha que guarda o parâmetro no campo:

= titular;

2. Eu mesmo

Complete para que o método devolva o próprio objeto:

return ;

3. O aviso

Qual é o código do aviso "Atribuição feita à mesma variável"?

4. Em kiwi.Vender(), quem é o this dentro de Vender?

5. O construtor tem preco = preco;, com um parâmetro e um campo chamados preco. O que acontece?

6. Por que this não pode ser usado num método static?


Resumo

  • this é o objeto que está executando o método. Estoque sozinho, dentro da classe, já é this.Estoque.
  • Quando um parâmetro tem o nome de um campo, ele faz sombra: nome = nome; não faz nada (CS1717). Use this.nome = nome;, ou o costume do C# (_nome, Nome).
  • outro.Metodo(this) passa o próprio objeto; return this; permite encadear chamadas.
  • A palavra também aparece em : this(…) (construtores), indexadores e métodos de extensão.
  • Não existe this em métodos static nem nas instruções de nível superior (CS0026).
  • Próxima aula: public, private e internal.