C# Classes e Objetos
Na aula O que é POO você viu a ideia: a classe é o molde e cada objeto é uma coisa feita com ele. Agora é hora de pôr a mão no código: escrever uma classe, criar objetos com new, entender o que a variável realmente guarda (uma referência) e o que acontece quando ela não guarda nada (null). No fim, você vai tirar a classe do arquivo principal e colocá-la num arquivo só dela.
- Como declarar uma classe e como criar objetos com
new, linha por linha. - Três jeitos de escrever a criação:
Produto p = new Produto();,var p = new Produto();eProduto p = new();. - Como criar vários objetos independentes da mesma classe.
- O que é uma referência: por que
Produto b = a;não cria um produto novo, e o que o==compara. - Objetos e
null: aNullReferenceExceptione os arrays de objetos, que nascem vazios. - Onde colocar a classe: no fim do mesmo arquivo ou num arquivo próprio (
Produto.cs), num projeto.
Os programas de arquivo único rodaram com dotnet run arquivo.cs, na pasta ~/cs; os de vários arquivos, num projeto em ~/projetos. Tudo na máquina fictícia da Ana (usuária ana, computador notebook), com o .NET 10 SDK.
Declarar uma classe
A lojinha da Ana vende frutas e outras coisas. Todo produto tem um nome e um preço. Essa é a descrição, o molde:
class Produto
{
public string Nome = "";
public decimal Preco;
}
- class
- Palavra que começa a declaração de uma classe ("classe", em inglês).
- Produto
- O nome da classe. Por convenção, começa com letra maiúscula e cada palavra nova também:
Produto,ContaBancaria,AlunoBolsista(o estilo se chama PascalCase). E o nome vai no singular: a classe descreve um produto. - { … }
- O corpo da classe. Tudo o que pertence a ela fica aqui dentro. Em C#, a chave de abertura vai sozinha na linha de baixo, como nos
ife métodos que você já escreveu. - public string Nome = "";
- Um campo: uma variável que cada produto vai ter.
publicdeixa o resto do programa usá-lo. O= ""é o valor inicial: todo produto novo nasce com o nome vazio (e nãonull; a aula Campos explica por quê). - public decimal Preco;
- Outro campo. Sem valor inicial, ele começa com o padrão do tipo:
0. Dinheiro sempre emdecimal.
Declarar a classe não cria produto nenhum. É como desenhar a ficha em branco: ela descreve como as fichas serão, mas ainda não há ficha preenchida. Criar é o próximo passo.
Criar objetos com new
Produto maca = new Produto();
maca.Nome = "maçã";
maca.Preco = 7.90m;
Produto banana = new Produto();
banana.Nome = "banana";
banana.Preco = 3.50m;
Console.WriteLine($"{maca.Nome} custa {maca.Preco:C}");
Console.WriteLine($"{banana.Nome} custa {banana.Preco:C}");
class Produto
{
public string Nome = "";
public decimal Preco;
}
$ dotnet run obj1.cs maçã custa R$ 7,90 banana custa R$ 3,50
A linha mais importante da aula é esta: Produto maca = new Produto();. Ela tem quatro partes:
- Produto
- O tipo da variável. Toda classe que você cria vira um tipo novo, como
intoustring. Esta variável só aceita produtos. - maca
- O nome da variável (camelCase, como toda variável local). Sem cedilha nem acento, por costume.
- new
- "Novo". Pede ao C# que crie um objeto na memória. Sem
new, nenhum objeto nasce. - Produto()
- Diz de qual classe é o objeto. Os parênteses chamam o construtor, um código que prepara o objeto recém-criado. Esta classe não escreveu nenhum, e o C# fornece um vazio. Você vai escrever os seus em Construtores.
Depois de criado, o objeto é acessado pelo ponto: maca.Nome = "maçã" lê-se "o nome da maçã recebe maçã". O :C em {maca.Preco:C} formata como dinheiro (veja Formatar Números). Cada new cria um objeto diferente: maca e banana são dois produtos, cada um com o seu nome e o seu preço. Mudar um não afeta o outro.
E se eu mostrar o objeto inteiro?
Acrescentando Console.WriteLine(maca); e Console.WriteLine(banana); ao programa:
$ dotnet run obj1.cs maçã custa R$ 7,90 banana custa R$ 3,50 Produto Produto
O C# não sabe como você quer mostrar um produto, então mostra só o nome da classe. Para mostrar algo como maçã (R$ 7,90), a classe precisa de um método especial, o ToString(), que você verá em A Classe object.
Três jeitos de escrever a criação
Produto p1 = new Produto();
var p2 = new Produto();
Produto p3 = new();
p1.Nome = "um"; p2.Nome = "dois"; p3.Nome = "três";
Console.WriteLine($"{p1.Nome} {p2.Nome} {p3.Nome}");
class Produto
{
public string Nome = "";
}
$ dotnet run formas.cs um dois três
| Forma | O que acontece | Quando usar |
|---|---|---|
Produto p1 = new Produto(); | A forma completa: tipo dos dois lados. | Sempre funciona; é a que você vê em todo código antigo. |
var p2 = new Produto(); | O tipo da variável é deduzido do new (veja Inferência com var). | Muito comum: o tipo já está escrito à direita. |
Produto p3 = new(); C# 9 | O contrário: o new() deduz a classe pelo tipo da variável. | Bom quando o tipo está à esquerda, como em campos. |
Os três criam um objeto igualzinho. Nesta parte do guia, a forma completa aparece mais, porque deixa tudo à vista.
Brinque com o código: crie um terceiro produto (uma uva de 12.00m) e mostre-o. Depois mostre a variável direto, com Console.WriteLine(uva);, e veja o nome da classe aparecer.
Produto maca = new Produto();
maca.Nome = "maçã";
maca.Preco = 7.90m;
Console.WriteLine($"{maca.Nome} custa {maca.Preco:C}");
class Produto
{
public string Nome = "";
public decimal Preco;
}
A variável guarda uma referência
Aqui está o ponto que mais confunde quem começa. Uma variável de tipo de valor (int, decimal) guarda o próprio valor. Uma variável de tipo classe não guarda o objeto: guarda uma referência, o "endereço" de onde o objeto está na memória. Pense no objeto como uma casa e na variável como um papel com o endereço da casa.
Veja o que acontece quando a Ana copia uma variável para outra:
Produto a = new Produto();
a.Nome = "maçã";
Produto b = a;
b.Nome = "pera";
Console.WriteLine($"a = {a.Nome}, b = {b.Nome}");
Console.WriteLine(a == b);
Produto c = new Produto();
c.Nome = "pera";
Console.WriteLine(b == c);
Console.WriteLine(b.Nome == c.Nome);
class Produto
{
public string Nome = "";
}
$ dotnet run ref.cs a = pera, b = pera « mudou pelo b, apareceu no a True « a e b apontam para o MESMO objeto False « b e c são objetos diferentes, mesmo com o mesmo nome True « os nomes (textos) são iguais
| Linha | O que acontece |
|---|---|
Produto a = new Produto(); | Um produto é criado (casa nº 1). a guarda o endereço "casa nº 1". |
Produto b = a; | Nenhum produto novo. b recebe uma cópia do endereço: agora a e b têm o mesmo papel, "casa nº 1". |
b.Nome = "pera"; | Vai até a casa nº 1 e troca o nome. Como a aponta para a mesma casa, a.Nome também é "pera". |
a == b | Com objetos de uma classe, == compara os endereços, não o conteúdo. Mesmo endereço: True. |
Produto c = new Produto(); | Um novo new: casa nº 2. Mesmo com o nome igual, b == c é False. |
b.Nome == c.Nome | Aqui são dois string. Para textos, o == do C# compara as letras: True. |
Esse comportamento tem nome: classes são tipos de referência. Os int, double e decimal são tipos de valor, e uma cópia deles é uma cópia de verdade. A diferença completa, com struct no meio, está em Valor × Referência. E é por isso que, ao passar um objeto para um método, o método mexe no mesmo objeto (veja Parâmetros).
Missão: o programa mostra a = pera, b = pera, mas a Ana queria dois produtos diferentes. Mude uma linha para que b seja um produto novo, e a saída seja a = maçã, b = pera.
Produto a = new Produto();
a.Nome = "maçã";
Produto b = a;
b.Nome = "pera";
Console.WriteLine($"a = {a.Nome}, b = {b.Nome}");
class Produto
{
public string Nome = "";
}
Produto a = new Produto();
a.Nome = "maçã";
Produto b = new Produto();
b.Nome = "pera";
Console.WriteLine($"a = {a.Nome}, b = {b.Nome}");
class Produto
{
public string Nome = "";
}
Objetos e null
Como toda referência, uma variável de classe pode valer null: o papel em branco, sem endereço nenhum (você viu isso com string em null e Tipos Anuláveis). Com as referências anuláveis ligadas, a variável que pode ficar em branco se declara com ?: Produto?. Mostrar um null funciona; o problema é tentar usar o objeto que não existe:
Produto? escolhido = null;
Console.WriteLine($"Escolhido: [{escolhido}]");
Console.WriteLine(escolhido.Nome);
class Produto
{
public string Nome = "";
}
$ dotnet run nulo2.cs; echo "código: $?" /home/ana/cs/nulo2.cs(3,19): warning CS8602: Desreferência de uma referência possivelmente nula. Escolhido: [] Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object. at Program.<Main>$(String[] args) in /home/ana/cs/nulo2.cs:line 3 código: 134
- warning CS8602
- O compilador avisou antes: na linha 3,
escolhidopode sernull. - Escolhido: []
- A linha 2 rodou: mostrar uma referência nula dá um texto vazio.
- System.NullReferenceException
- "Exceção de referência nula": você tentou ir até a casa de um endereço em branco. "
Object reference not set to an instance of an object" = "a referência não aponta para nenhum objeto". - line 3
- Onde aconteceu. É sempre por aqui que você começa a investigar.
- código: 134
- O programa morreu por uma exceção não tratada.
Um array de objetos nasce vazio
Um caso muito comum: new Produto[3] cria o array com 3 posições, não os produtos. Cada posição começa com null:
Produto[] cesta = new Produto[3];
Console.WriteLine($"[{cesta[0]}]");
cesta[0] = new Produto();
cesta[0].Nome = "maçã";
Console.WriteLine(cesta[0].Nome);
Console.WriteLine(cesta[1].Nome);
class Produto
{
public string Nome = "";
}
$ dotnet run cesta.cs; echo "código: $?" [] « a posição 0 ainda está vazia maçã « depois do new, tem um produto Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object. at Program.<Main>$(String[] args) in /home/ana/cs/cesta.cs:line 6 código: 134
A posição 0 recebeu um produto com new; a posição 1 continuou null, e a linha 6 quebrou. Repare que, desta vez, não houve aviso nenhum: o compilador não acompanha cada posição de um array, e trata cesta[1] como se tivesse um produto. É um dos lugares em que as referências anuláveis não protegem você. Regra: depois de new Produto[n], preencha cada posição com new Produto() antes de usar (um for resolve).
null × "sem valor"
São coisas diferentes. Produto? escolhido = null; é uma variável com valor (o valor "nada"); o erro só aparece ao rodar. Já Produto escolhido;, sem valor nenhum, seguido de escolhido.Nome = "uva";, nem compila:
$ dotnet run semvalor.cs /home/ana/cs/semvalor.cs(2,1): error CS0165: Uso de variável local não atribuída "escolhido" Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
Onde colocar a classe
Forma 1: no fim do mesmo arquivo
Foi o que você fez até aqui: as instruções em cima, as classes embaixo, tudo num arquivo só, rodando com dotnet run arquivo.cs. Se a classe vier antes das instruções, é o erro CS8803 que você viu em O que é POO. Entre si, as classes podem estar em qualquer ordem.
Forma 2: um arquivo por classe, num projeto (a forma profissional)
Em programas de verdade, cada classe fica no seu próprio arquivo, com o mesmo nome da classe. Para isso, a Ana precisa de um projeto: o dotnet run arquivo.cs compila só aquele arquivo.
$ cd ~/projetos $ dotnet new console -o Loja O modelo "Aplicativo do Console" foi criado com êxito. … $ cd Loja
Ela escreveu a classe em Produto.cs e trocou o conteúdo do Program.cs:
class Produto
{
public string Nome = "";
public decimal Preco;
}
Produto maca = new Produto();
maca.Nome = "maçã";
maca.Preco = 7.90m;
Console.WriteLine($"{maca.Nome} custa {maca.Preco:C}");
$ ls Loja.csproj obj Produto.cs Program.cs $ dotnet run maçã custa R$ 7,90
- dotnet run
- Sem nome de arquivo: compila o projeto inteiro, ou seja, todos os
.csda pasta, juntos. Por isso oProgram.csenxerga a classeProdutodo outro arquivo, sem nenhuma linha extra. - Produto.cs
- Só tem a classe, sem instruções. O nome do arquivo igual ao da classe não é obrigatório, mas é o costume, e ajuda a achar cada classe.
- Program.cs
- O único arquivo com instruções de nível superior. Num projeto, só um arquivo pode tê-las (senão, erro
CS8802, abaixo).
Se a Ana tentar fazer isso sem projeto, com dois arquivos soltos numa pasta e dotnet run app.cs, o compilador nem olha o outro arquivo:
$ ls app.cs produto.cs $ dotnet run app.cs /home/ana/cs/loja/app.cs(1,1): error CS0246: O nome do tipo ou do namespace "Produto" não pode ser encontrado (está faltando uma diretiva using ou uma referência de assembly?) /home/ana/cs/loja/app.cs(1,17): error CS0246: O nome do tipo ou do namespace "Produto" não pode ser encontrado (está faltando uma diretiva using ou uma referência de assembly?) Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
Qual usar?
| Forma | Vantagens | Quando usar |
|---|---|---|
Classes no fim do arquivo, com dotnet run arquivo.cs | Tudo à vista num lugar só; nada para configurar | Exemplos, exercícios, programas pequenos (como nesta parte do guia) |
| Um arquivo por classe, num projeto | Fácil de achar cada classe; é o padrão do Visual Studio, do VS Code e de toda empresa | Qualquer programa que vá crescer |
No editor do guia, os vários arquivos aparecem como abas da mesma caixa, e todos são compilados juntos, como num projeto. A missão final usa isso.
Quando dá errado
NullReferenceException
$ dotnet run nulo2.cs; echo "código: $?" /home/ana/cs/nulo2.cs(3,19): warning CS8602: Desreferência de uma referência possivelmente nula. Escolhido: [] Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object. at Program.<Main>$(String[] args) in /home/ana/cs/nulo2.cs:line 3 código: 134
O que aconteceu: a variável vale null e o programa tentou usar um campo ou método dela.
Como resolver: vá à linha indicada e descubra qual variável está nula. Em geral faltou um new, ou uma posição de array ainda não recebeu objeto. Se null é um valor possível, teste antes (if (escolhido is not null)) ou use escolhido?.Nome (veja Operadores ??, ?. e ??=).
$ dotnet run typo.cs /home/ana/cs/typo.cs(1,1): error CS0246: O nome do tipo ou do namespace "produto" não pode ser encontrado (está faltando uma diretiva using ou uma referência de assembly?) /home/ana/cs/typo.cs(1,20): error CS0246: O nome do tipo ou do namespace "produto" não pode ser encontrado (está faltando uma diretiva using ou uma referência de assembly?) /home/ana/cs/typo.cs(7,20): warning CS0649: Campo "Produto.Preco" nunca é atribuído e sempre terá seu valor padrão 0 Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
O que aconteceu: a linha 1 era produto maca = new produto();, com p minúsculo. O C# diferencia maiúsculas de minúsculas: produto e Produto são nomes diferentes. O erro aparece duas vezes porque o nome é usado duas vezes na linha (no tipo e no new). O mesmo erro aparece quando a classe está noutro arquivo que não é compilado junto.
Como resolver: confira a grafia exata do nome da classe. Se ela está noutro arquivo, use um projeto.
$ dotnet run typo2.cs /home/ana/cs/typo2.cs(2,6): error CS1061: ‘Produto’ não contém uma definição para "Preço" e não foi possível encontrar nenhum método de extensão "Preço" que aceite um primeiro argumento do tipo ‘Produto’ (você está se esquecendo de usar uma diretiva ou uma referência de assembly?) /home/ana/cs/typo2.cs(7,20): warning CS0649: Campo "Produto.Preco" nunca é atribuído e sempre terá seu valor padrão 0 Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
O que aconteceu: maca.Preço = 7.90m;, com cedilha. O campo se chama Preco. Para o compilador, são nomes diferentes, e a classe Produto não tem nada chamado Preço. O aviso CS0649 confirma: o Preco verdadeiro nunca recebeu valor.
Como resolver: use o nome exato do campo. No VS Code, digite maca. e espere a lista de membros aparecer.
$ dotnet run /home/ana/projetos/Estante/Program.cs(1,1): error CS8802: Apenas uma unidade de compilação pode ter instruções de nível superior. [/home/ana/projetos/Estante/Estante.csproj] Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
O que aconteceu: num projeto, além do Program.cs, o arquivo Livro.cs também tinha uma instrução solta (Console.WriteLine("oi");) antes da classe. "Unidade de compilação" é cada arquivo .cs. Só um pode ser o "começo" do programa.
Como resolver: deixe as instruções só no Program.cs; os outros arquivos têm apenas classes.
$ dotnet run semvalor.cs /home/ana/cs/semvalor.cs(2,1): error CS0165: Uso de variável local não atribuída "escolhido" Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
O que aconteceu: Produto escolhido; declara a variável, mas não cria objeto nem dá null. Usá-la na linha 2 é proibido.
Como resolver: Produto escolhido = new Produto();.
Referência
| Escrita | Significado |
|---|---|
class Nome { … } | Declara uma classe (nome em PascalCase, no singular) |
public tipo Campo = valor; | Um campo que cada objeto tem, visível de fora |
Nome v = new Nome(); | Cria um objeto e guarda a referência dele em v |
var v = new Nome(); / Nome v = new(); | O mesmo, com o tipo deduzido de um dos lados |
v.Campo | Lê ou muda um campo do objeto |
Nome b = a; | Copia a referência: a e b apontam para o mesmo objeto |
a == b | Com objetos de classe: True só se for o mesmo objeto |
Nome? v = null; | Variável sem objeto; v.Campo dá NullReferenceException |
new Nome[3] | Array com 3 posições null (não cria os objetos) |
Console.WriteLine(v) | Mostra o nome da classe, até a classe ter um ToString() |
dotnet run num projeto | Compila todos os .cs da pasta juntos |
Missão final: a estante da Ana, em dois arquivos
Missão: esta caixa tem dois arquivos (veja as abas), como um projeto. Em Livro.cs, complete a classe com os campos public string Titulo = ""; e public string Autor = "";. Em Program.cs, crie dois livros (Dom Casmurro, de Machado de Assis, e A Hora da Estrela, de Clarice Lispector) e mostre cada um no formato Dom Casmurro, de Machado de Assis.
Console.WriteLine("Estante da Ana");
// crie os dois livros aqui
class Livro
{
// campos aqui
}
Console.WriteLine("Estante da Ana");
Livro l1 = new Livro();
l1.Titulo = "Dom Casmurro";
l1.Autor = "Machado de Assis";
Livro l2 = new Livro();
l2.Titulo = "A Hora da Estrela";
l2.Autor = "Clarice Lispector";
Console.WriteLine($"{l1.Titulo}, de {l1.Autor}");
Console.WriteLine($"{l2.Titulo}, de {l2.Autor}");
class Livro
{
public string Titulo = "";
public string Autor = "";
}
Exercícios
1. Declarar
Complete a primeira linha da declaração de uma classe chamada Conta:
2. Criar
Complete para criar um objeto Conta e guardá-lo em c:
3. Quantos objetos?
Quantos objetos Produto existem depois destas linhas? Produto a = new Produto(); Produto b = a; Produto c = new Produto(); Produto d = c;
4. Depois de Produto b = a; b.Nome = "kiwi";, quanto vale a.Nome?
5. O que Produto[] cesta = new Produto[3]; cria?
6. Dois arquivos soltos, app.cs e produto.cs, e dotnet run app.cs dá CS0246. Por quê?
Resumo
class Produto { … }declara o molde;new Produto()cria cada objeto. Acesse com o ponto:maca.Nome.Produto p = new Produto();,var p = new Produto();eProduto p = new();fazem a mesma coisa.- A variável guarda uma referência:
b = anão copia o objeto, ea == bcompara endereços. - Uma referência pode ser
null(Produto?); usar o ponto nela dáNullReferenceException, código 134.new Produto[3]cria só posições vazias, e sem aviso. - Classes ficam no fim do arquivo (
CS8803) ou em arquivos próprios, num projeto (dotnet runcompila todos; só um arquivo com instruções, senãoCS8802). - Próxima aula: Campos, as variáveis de cada objeto.