C# static
Até aqui, cada campo e cada propriedade pertenciam a um objeto: a Ana tem o nome dela, o Bruno tem o dele. Mas algumas coisas não são de ninguém em particular: o total de alunos da escola, a fórmula que converte graus Celsius em Fahrenheit, o número π. Para elas, o C# tem a palavra static ("estático"): o membro passa a pertencer à classe, e existe uma vez só, não importa quantos objetos você crie.
- A diferença entre membro de instância (de cada objeto) e membro static (da classe).
- Como contar objetos criados com um campo ou uma propriedade
static. - Que, em C#, um membro
staticse usa só pelo nome da classe (CS0176). - Métodos
static, comoMath.MaxeConsole.WriteLine, e como criar os seus. - O que um método
staticnão pode usar (CS0120). - Classes
static(CS0712,CS0708), o construtorstatice ousing static. - Por que o
Mainéstatic.
A ideia: o caderno de cada aluno e o quadro da sala
Numa sala de aula, cada aluno tem o seu caderno. O que a Ana escreve no caderno dela não aparece no caderno do Bruno. Mas a sala tem um quadro só, na parede. Quando a professora escreve "prova na sexta" no quadro, todo mundo vê a mesma coisa, e ninguém precisa ter uma cópia.
- Os cadernos são os membros de instância: cada objeto tem os seus (
Nome,Nota). - O quadro é um membro
static: pertence à classe, existe uma vez só e é o mesmo para todos.
Uma instância é um objeto criado com new. "Membro de instância" quer dizer "membro que cada objeto tem o seu".
O contador de alunos
A Ana quer saber quantos alunos foram matriculados. Primeiro, a tentativa ingênua: uma propriedade comum, que cada aluno aumenta no construtor.
var ana = new Aluno("Ana");
var bruno = new Aluno("Bruno");
var carla = new Aluno("Carla");
Console.WriteLine($"Ana conta: {ana.Total}");
Console.WriteLine($"Carla conta: {carla.Total}");
class Aluno
{
public int Total { get; private set; }
public Aluno(string nome)
{
Nome = nome;
Total++;
}
public string Nome { get; }
}
$ dotnet run sem-static.cs Ana conta: 1 Carla conta: 1 « cada aluno contou só a si mesmo
Cada objeto tem o seu Total, que começa em 0 e vai para 1. É como se cada aluno anotasse "1 aluno" no próprio caderno. Para ter um número só, o total tem de ir para o quadro: static.
var ana = new Aluno("Ana");
var bruno = new Aluno("Bruno");
var carla = new Aluno("Carla");
Console.WriteLine(ana.Nome);
Console.WriteLine(carla.Nome);
Console.WriteLine($"Alunos matriculados: {Aluno.Total}");
class Aluno
{
public static int Total { get; private set; }
public Aluno(string nome)
{
Nome = nome;
Total++;
}
public string Nome { get; }
}
$ dotnet run contador.cs Ana Carla Alunos matriculados: 3
- public static int Total { get; private set; }
- Com
static, existe umTotalpara a classe inteira, criado antes de qualquer aluno. Ele começa em 0. - Total++; no construtor
- Cada
new Aluno(…)soma 1 no mesmoTotal. Depois de três alunos, ele vale 3. - public string Nome { get; }
- Sem
static: cada aluno tem o seu nome. Isso continua igual. - Aluno.Total
- Um membro
staticse usa pelo nome da classe, e não por um objeto. É como ler o quadro da sala: você não precisa pegar o caderno de ninguém.
Pelo objeto não dá
Em algumas linguagens (como Java), dá para escrever ana.Total mesmo com Total sendo static. Em C#, não: o compilador exige o nome da classe, para ninguém achar que o total é "da Ana".
var ana = new Aluno("Ana");
Console.WriteLine(ana.Total);
class Aluno
{
public static int Total { get; private set; }
public Aluno(string nome)
{
Nome = nome;
Total++;
}
public string Nome { get; }
}
$ dotnet run pelo-objeto.cs /home/ana/cs/pelo-objeto.cs(2,19): error CS0176: O membro "Aluno.Total" não pode ser acessado com uma referência de instância; qualifique-o com um nome de tipo Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
"Qualifique-o com um nome de tipo" quer dizer: escreva o nome da classe na frente, Aluno.Total. Dentro da própria classe (como no construtor), o nome sozinho, Total, basta.
Missão: a padaria da Ana numera os pedidos, mas todos estão saindo com o número 1. Rode e veja. Depois, deixe o campo _contador static, para que ele seja um só para todos os pedidos, e rode de novo.
var p1 = new Pedido("pão");
var p2 = new Pedido("leite");
var p3 = new Pedido("café");
Console.WriteLine($"Pedido {p1.Numero}: {p1.Item}");
Console.WriteLine($"Pedido {p2.Numero}: {p2.Item}");
Console.WriteLine($"Pedido {p3.Numero}: {p3.Item}");
class Pedido
{
private int _contador;
public Pedido(string item)
{
_contador++;
Numero = _contador;
Item = item;
}
public int Numero { get; }
public string Item { get; }
}
var p1 = new Pedido("pão");
var p2 = new Pedido("leite");
var p3 = new Pedido("café");
Console.WriteLine($"Pedido {p1.Numero}: {p1.Item}");
Console.WriteLine($"Pedido {p2.Numero}: {p2.Item}");
Console.WriteLine($"Pedido {p3.Numero}: {p3.Item}");
class Pedido
{
private static int _contador;
public Pedido(string item)
{
_contador++;
Numero = _contador;
Item = item;
}
public int Numero { get; }
public string Item { get; }
}
Repare na mistura: _contador é do quadro (um só), e Numero é do caderno (cada pedido guarda o número que recebeu). É um padrão muito comum para dar números de série a objetos.
Métodos static
Você usa métodos static desde a primeira aula. Console.WriteLine é um: você nunca escreveu new Console(). Math.Max e int.Parse também. Eles são da classe porque não dependem de nenhum objeto: para achar o maior entre 3 e 8, ninguém precisa de "uma matemática" criada com new.
Criando os seus
A Ana quer converter temperaturas. A conta só depende do número que entra; não há nada para guardar num objeto. É um caso perfeito para static:
Console.WriteLine(Conversor.CelsiusParaFahrenheit(25));
Console.WriteLine(Conversor.CelsiusParaFahrenheit(-10));
Console.WriteLine(Math.Max(3, 8));
static class Conversor
{
public static double CelsiusParaFahrenheit(double celsius)
{
return celsius * 9 / 5 + 32;
}
}
$ dotnet run conversor.cs 77 14 8
- public static double CelsiusParaFahrenheit(double celsius)
- Um método
static: chama-se pela classe,Conversor.CelsiusParaFahrenheit(25), sem criar objeto. - static class Conversor
- Uma classe static: ela só pode ter membros
statice nunca vira objeto. É uma "caixa de ferramentas". A classeMathdo .NET é assim.
O que uma classe static não deixa fazer
Como ela nunca vira objeto, new é proibido:
$ dotnet run new-static.cs « com var c = new Conversor(); /home/ana/cs/new-static.cs(1,9): error CS0712: Não é possível criar uma instância da classe estática "Conversor" /home/ana/cs/new-static.cs(1,1): error CS0723: Não é possível declarar uma variável de tipo static "Conversor" Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
E um membro sem static dentro dela também:
Console.WriteLine(Conversor.Dobro(4));
static class Conversor
{
public static int Dobro(int n)
{
return n * 2;
}
public int Triplo(int n)
{
return n * 3;
}
}
$ dotnet run membro-instancia.cs /home/ana/cs/membro-instancia.cs(10,16): error CS0708: "Triplo": não pode declarar membros de instância em uma classe estática Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
O Triplo seria "do caderno", mas uma classe static não tem cadernos. Conserto: public static int Triplo.
O que um método static não pode usar
Um método static roda sem objeto nenhum. Por isso, lá dentro, não existe this e não existem os campos de instância: de qual objeto ele leria?
Console.WriteLine(Loja.Resumo());
class Loja
{
private string _nome = "Lojinha da Ana";
public static string Resumo()
{
return $"Bem-vindo à {_nome}";
}
}
$ dotnet run cs0120.cs /home/ana/cs/cs0120.cs(9,31): error CS0120: Uma referência de objeto é necessária para o campo, o método ou a propriedade "Loja._nome" não estática Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
"Uma referência de objeto é necessária": para ler _nome, é preciso um objeto Loja, e o método static não tem nenhum. Há dois consertos, e a escolha depende do que o dado é:
- Se o nome é de cada loja (a Ana pode ter duas lojas), tire o
staticdo método e chame por um objeto:new Loja().Resumo(). - Se o nome é um só para o programa inteiro, deixe o campo
statictambém:private static string _nome = "…";.
O caminho contrário sempre funciona: um método de instância pode usar membros static à vontade (o construtor do Aluno usou o Total). Todo aluno enxerga o quadro.
| De dentro de… | Usa membro de instância? | Usa membro static? |
|---|---|---|
Método de instância (sem static) | Sim | Sim |
Método static | Não (CS0120), só por um objeto | Sim |
Por que o Main é static
Quando um programa começa, ainda não existe objeto nenhum. Alguém precisa rodar primeiro, sem depender de um new. Por isso, no formato clássico (veja Program.cs e o Main), o ponto de partida é um método static:
class Program
{
static void Main()
{
Console.WriteLine(Dobro(21));
}
static int Dobro(int n)
{
return n * 2;
}
}
$ dotnet run main.cs 42
Esquecendo o static do Dobro, o Main (que é static) não consegue chamá-lo, com o mesmo erro da seção anterior:
$ dotnet run main-erro.cs /home/ana/cs/main-erro.cs(5,27): error CS0120: Uma referência de objeto é necessária para o campo, o método ou a propriedade "Program.Dobro(int)" não estática Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
Esse é um tropeço clássico de quem vem dos programas de um arquivo só, em que as funções escritas no topo do arquivo funcionam sem static nenhum. Por baixo dos panos, o compilador põe essas instruções de nível superior dentro de um Main static para você.
O construtor static
Às vezes um dado static precisa ser preparado antes do primeiro uso: ler uma configuração, montar uma tabela. Para isso existe o construtor static: ele roda uma vez só, sozinho, logo antes da primeira vez que a classe é usada.
Console.WriteLine("Começou o programa");
Console.WriteLine($"Taxa: {Loja.Taxa}");
Console.WriteLine($"Taxa de novo: {Loja.Taxa}");
class Loja
{
public static decimal Taxa { get; }
static Loja()
{
Console.WriteLine("(construtor static da Loja rodou)");
Taxa = 0.05m;
}
}
$ dotnet run construtor-static.cs Começou o programa (construtor static da Loja rodou) Taxa: 0,05 Taxa de novo: 0,05 « na segunda vez, ele não roda de novo
- static Loja()
- O nome da classe, sem modificador de acesso e sem parâmetros. Ninguém o chama: o .NET chama sozinho.
- A ordem da saída
- "Começou o programa" vem antes: o construtor static só roda quando a
Lojaé usada pela primeira vez, e nunca mais.
using static: sem repetir o nome da classe
Se um arquivo usa muito uma classe static, dá para "importar" os membros dela e escrever só o nome do método:
using static System.Math; Console.WriteLine(Max(3, 8)); Console.WriteLine(Sqrt(81)); Console.WriteLine(PI);
$ dotnet run using-static.cs 8 9 3,141592653589793
É cômodo em contas com muita matemática. Use com moderação: num arquivo grande, Max(…) sozinho deixa o leitor sem saber de onde aquilo veio.
const) já é static por natureza: Math.PI é uma const da classe Math, e você a lê pelo nome da classe. Para um valor da classe que é calculado quando o programa roda, use static readonly (veja readonly e init).Quando usar static
| Situação | static? | Exemplo |
|---|---|---|
| O dado é diferente para cada objeto | Não | Nome, Nota, Saldo |
| O dado é um só para a classe inteira | Sim | Aluno.Total, um contador de pedidos |
| O método só usa os parâmetros, nada do objeto | Sim | Conversor.CelsiusParaFahrenheit, Math.Max |
| O método lê ou muda os campos do objeto | Não | conta.Depositar(…) |
| Uma "caixa de ferramentas" sem dados | static class | Math, Conversor |
static que qualquer parte do programa altera é, na prática, uma variável global: difícil de testar e de entender. Prefira static para contas (métodos) e para valores que não mudam. Contadores como o Total funcionam bem com private set, para que só a classe mude.Quando dá errado
O que aconteceu: você usou um membro static por um objeto (ana.Total).
Como resolver: use o nome da classe: Aluno.Total.
O que aconteceu: um método static (às vezes o Main) tentou usar um membro de instância. Também acontece ao escrever Aluno.Nome, pelo nome da classe, quando Nome é de cada aluno.
Como resolver: use um objeto (ana.Nome, new Loja().Resumo()) ou, se o dado é mesmo da classe, deixe-o static.
O que aconteceu: new numa static class. O CS0723 vem junto, por causa da variável.
Como resolver: não crie objeto: chame direto, Conversor.CelsiusParaFahrenheit(25).
O que aconteceu: um método ou campo sem static dentro de uma static class.
Como resolver: escreva static nele ou, se a classe precisa mesmo de objetos, tire o static da classe.
Referência
| Escrita | Significado |
|---|---|
private static int _contador; | Campo da classe: um só, para todos os objetos |
public static int Total { get; private set; } | Propriedade da classe |
public static double Dobro(double n) | Método da classe; chama-se Classe.Dobro(…) |
static class Conversor | Classe só com membros static; não aceita new |
static Loja() { … } | Construtor static: roda uma vez, antes do primeiro uso |
using static System.Math; | Usa Max, Sqrt, PI sem escrever Math. |
Aluno.Total | Acesso a membro static: sempre pelo nome da classe |
Missão final: a calculadora de descontos
Missão: complete a classe static class Preco com o método public static decimal ComDesconto(decimal valor, int porcento), que devolve o valor com o desconto aplicado: valor * (100 - porcento) / 100. O programa de cima já chama o método pelo nome da classe.
Console.WriteLine(Preco.ComDesconto(100m, 10).ToString("C"));
Console.WriteLine(Preco.ComDesconto(59.90m, 50).ToString("C"));
static class Preco
{
// escreva o método aqui
}
Console.WriteLine(Preco.ComDesconto(100m, 10).ToString("C"));
Console.WriteLine(Preco.ComDesconto(59.90m, 50).ToString("C"));
static class Preco
{
public static decimal ComDesconto(decimal valor, int porcento)
{
return valor * (100 - porcento) / 100;
}
}
Exercícios
1. Da classe
Qual palavra faz um campo pertencer à classe, e não a cada objeto?
2. Chamar pela classe
A classe Conversor tem o método static Dobro(int n). Escreva a chamada que dobra o 7 (sem o ponto e vírgula).
3. O erro do objeto
Qual é o código do erro ao usar um membro static por um objeto, como em ana.Total?
4. Três objetos Aluno foram criados, e cada construtor fez Total++ numa propriedade sem static. Quanto vale o Total de cada um?
5. Um método static tenta ler um campo de instância. O que acontece?
6. Quando roda o construtor static de uma classe?
Resumo
- Membro de instância: cada objeto tem o seu (o caderno). Membro
static: um só, da classe (o quadro). - Membros
staticse usam pelo nome da classe (Aluno.Total,Math.Max). Pelo objeto, éCS0176. - Método
staticnão enxerga campos de instância nemthis(CS0120). O contrário sempre funciona. static classé uma caixa de ferramentas: só membros static, semnew(CS0712,CS0708).- O construtor
staticroda uma vez, antes do primeiro uso.using staticdispensa o nome da classe. - O
Mainéstaticporque roda antes de existir qualquer objeto.