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.
- O que
thisé, e por que ele já estava "escondido" emEstoque = Estoque - 1;. - O problema da sombra:
nome = nome;não faz o que parece (avisoCS1717), e o consertothis.nome = nome;. - Por que o costume do C# (
_nomeeNome) quase sempre dispensa othis. - Como passar o próprio objeto para outro método:
turma.Matricular(this). - Como devolver
thispara encadear chamadas:pedido.Adicionar("pão").Adicionar("leite"). - Os outros usos da palavra (
: this(…), indexadores, extensões) e os errosCS0023eCS0026.
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
preconunca recebeu valor. (Onomenã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
nomedeste objeto". Othis.pula a sombra e vai direto ao campo. - = nome;
- Sem
this, é o parâmetro: o valor que chegou nonew Produto("maçã", …). - {nome} no Mostrar()
- Aqui não há parâmetro com esse nome, então não há sombra:
nomejá é o campo, e othis.é opcional.
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.
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
thisvai como argumento, e do outro lado chega no parâmetroaluno. 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:
AlunousaTurma, 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 opedido; no resultado, chama-se.Adicionar("leite"); e assim por diante. É o mesmo que três linhaspedido.Adicionar(…);separadas.
Outros lugares onde aparece a palavra this
| Escrita | Significado | Aula |
|---|---|---|
this.campo, this.Metodo() | Um membro deste objeto | esta |
Metodo(this), return this; | O objeto inteiro, como valor | esta |
public C(…) : this(…) | Um construtor chamando outro da mesma classe | Construtores |
public T this[int i] { get; set; } | Um indexador: o objeto se usa com [ ], como um array | Indexadores |
static … Metodo(this string texto) | Um método de extensão: "acrescenta" um método a um tipo que já existe | Mé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
$ 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;.
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.
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.
$ 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
| Escrita | Significado |
|---|---|
this | Dentro 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 superior | Erro 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:
2. Eu mesmo
Complete para que o método devolva o próprio objeto:
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.Estoquesozinho, 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). Usethis.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
thisem métodosstaticnem nas instruções de nível superior (CS0026). - Próxima aula: public, private e internal.