C# public, private e internal

Até aqui, você escreveu public na frente de quase tudo, sem pensar muito. Essa palavra é um modificador de acesso: ela diz quem pode usar um campo, um método ou uma classe. Nesta aula você conhece todos os modificadores do C#, descobre o que vale quando você não escreve nenhum (o "padrão") e aprende a ler o erro mais comum do assunto, o CS0122.

Você vai aprender
  • O que é um modificador de acesso e por que ele existe.
  • private ("privado"): só a própria classe usa.
  • public ("público"): qualquer código usa.
  • internal ("interno"): só o mesmo projeto usa.
  • protected ("protegido"): a classe e as classes filhas.
  • O padrão de cada lugar: membro de classe sem modificador é private; classe sem modificador é internal.
  • Os erros reais CS0122, CS1527 e CS0050, e como consertar cada um.

Esta aula usa o que você viu em Classes e Objetos, Campos e Métodos.


A ideia antes do código: as portas da escola

Pense na escola da Ana. Nem toda porta abre para todo mundo:

  • O portão da rua abre para qualquer pessoa: pais, visitantes, entregadores. Isso é public.
  • O corredor das salas é só para quem é da escola: alunos e professores. Quem vem de outra escola não entra. Isso é internal (o "da escola" é o seu projeto).
  • O armário da família do zelador abre para ele e para os filhos dele. Isso é protected (os "filhos" são as classes que herdam).
  • A gaveta da mesa da diretora só ela abre. Isso é private.

Num programa é igual. Uma classe guarda coisas que só ela deveria mexer (o dinheiro de um cofre, a senha de uma porta) e oferece algumas portas abertas para o resto do programa. O modificador de acesso é a placa em cada porta. E quem confere as placas é o compilador: se alguém tenta entrar por uma porta fechada, o programa nem compila.


private: só a própria classe

A Ana tem um cofrinho. O dinheiro fica guardado lá dentro, e a única forma de mexer nele é pelo método Guardar:

var cofre = new Cofre();
cofre.Guardar(50m);
cofre.Guardar(20m);
Console.WriteLine($"No cofre: {cofre.Total()}");

class Cofre
{
    private decimal _dinheiro;

    public void Guardar(decimal valor)
    {
        _dinheiro += valor;
    }

    public decimal Total()
    {
        return _dinheiro;
    }
}
$ dotnet run cofre.cs
No cofre: 70
private decimal _dinheiro;
O campo é private: só o código dentro das chaves de class Cofre pode ler ou mudar _dinheiro. O _ no começo do nome é a convenção da Microsoft para campos privados: quem lê o código já sabe que aquilo é "coisa de dentro".
public void Guardar(decimal valor)
O método é public: qualquer código pode chamá-lo. Ele está dentro da classe, então pode mexer em _dinheiro.
public decimal Total()
Outra porta aberta, só para ler: devolve o valor guardado.
cofre.Guardar(50m);
As instruções de cima ficam fora da classe. Elas usam só as portas public.

Tentando abrir a gaveta

Agora alguém tenta escrever direto no campo privado, por fora:

var cofre = new Cofre();
cofre.Guardar(50m);
cofre._dinheiro = 1000000m;
Console.WriteLine($"No cofre: {cofre.Total()}");

class Cofre
{
    private decimal _dinheiro;

    public void Guardar(decimal valor)
    {
        _dinheiro += valor;
    }

    public decimal Total()
    {
        return _dinheiro;
    }
}
$ dotnet run cofre-erro.cs
/home/ana/cs/cofre-erro.cs(3,7): error CS0122: "Cofre._dinheiro" é inacessível devido ao seu nível de proteção

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

Leia a mensagem por partes:

  • cofre-erro.cs(3,7): linha 3, coluna 7, exatamente onde aparece o _dinheiro.
  • CS0122: o código do erro. Em inglês, a mensagem é 'Cofre._dinheiro' is inaccessible due to its protection level.
  • "inacessível devido ao seu nível de proteção": o campo existe, mas a placa da porta diz private, e você está do lado de fora.

Repare que o erro aparece antes de o programa rodar. Ninguém consegue colocar um milhão no cofre da Ana "por engano": o compilador barra. Para consertar, use a porta aberta: cofre.Guardar(…).

Métodos private: ajudantes internos

Métodos também podem ser private. Isso é útil para ajudantes: pedaços de código que a classe usa por dentro, mas que não fazem sentido para quem está de fora. O caixa da lojinha da Ana registra cada venda com um método ajudante:

var caixa = new Caixa();
caixa.Vender(12.50m);
caixa.Vender(-3m);
caixa.Vender(7.25m);
Console.WriteLine($"Total do dia: {caixa.Total():C}");

class Caixa
{
    private decimal _total;

    public void Vender(decimal valor)
    {
        if (valor <= 0)
        {
            Registrar($"Venda recusada: {valor:C}");
            return;
        }
        _total += valor;
        Registrar($"Venda de {valor:C}");
    }

    public decimal Total()
    {
        return _total;
    }

    private void Registrar(string texto)
    {
        Console.WriteLine($"[caixa] {texto}");
    }
}
$ dotnet run caixa.cs
[caixa] Venda de R$ 12,50
[caixa] Venda recusada: -R$ 3,00
[caixa] Venda de R$ 7,25
Total do dia: R$ 19,75

Vender chama Registrar sem problema, porque os dois estão dentro da mesma classe. Mas, de fora, caixa.Registrar("tentando de fora"); dá o mesmo erro de antes:

$ dotnet run ajudante.cs
/home/ana/cs/ajudante.cs(3,7): error CS0122: "Caixa.Registrar(string)" é inacessível devido ao seu nível de proteção

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

A vantagem: amanhã a Ana pode trocar o Registrar para gravar num arquivo em vez de mostrar na tela, ou até apagá-lo. Como ninguém de fora usa esse método, nada quebra fora da classe.


O padrão: quando você não escreve nada

Você pode declarar um campo ou um método sem modificador nenhum. Ele não fica "sem proteção": o C# aplica o modificador padrão, e o padrão de quem está dentro de uma classe é o mais fechado de todos, private.

var cofre = new Cofre();
cofre.Guardar(50m);
Console.WriteLine(cofre._dinheiro);

class Cofre
{
    decimal _dinheiro;

    public void Guardar(decimal valor)
    {
        _dinheiro += valor;
    }
}
$ dotnet run padrao.cs
/home/ana/cs/padrao.cs(3,25): error CS0122: "Cofre._dinheiro" é inacessível devido ao seu nível de proteção

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

decimal _dinheiro; sem modificador é o mesmo que private decimal _dinheiro;. Com métodos acontece igual, e esse é um susto comum de quem está começando: esquecer o public num método que deveria ser chamado de fora.

Onde está a declaraçãoSem modificador, vale…Exemplo
Campo, método, propriedade ou construtor de uma classe (ou struct)privatedecimal _dinheiro;
Classe dentro de outra classe (classe aninhada)privateclass Cofre { class Chave { } }
Classe, struct, record, enum ou interface "solta" no arquivo (ou num namespace)internalclass Cofre { }
Membro de uma interfacepublicinterface IVendavel { decimal Preco(); }
Valor de um enumpublic (não aceita outro)enum Cor { Azul, Verde }
Escreva sempre o modificador. O padrão funciona, mas quem lê o código precisa lembrar a regra. Com private escrito, ninguém tem dúvida. É o que a própria Microsoft recomenda nos seus guias de estilo.

Missão: o programa não compila, porque o método Abrir está sem modificador (e, por isso, é private). Clique em Rodar e leia o erro. Depois, deixe Abrir público, sem abrir a senha, e rode de novo.

var porta = new Porta();
porta.Abrir("1234");
porta.Abrir("0000");

class Porta
{
    private string _senha = "1234";

    void Abrir(string tentativa)
    {
        if (tentativa == _senha)
        {
            Console.WriteLine("Porta aberta!");
        }
        else
        {
            Console.WriteLine("Senha errada.");
        }
    }
}
var porta = new Porta();
porta.Abrir("1234");
porta.Abrir("0000");

class Porta
{
    private string _senha = "1234";

    public void Abrir(string tentativa)
    {
        if (tentativa == _senha)
        {
            Console.WriteLine("Porta aberta!");
        }
        else
        {
            Console.WriteLine("Senha errada.");
        }
    }
}

public: qualquer código

public é o portão da rua: qualquer código, de qualquer classe, de qualquer projeto que use o seu, pode chamar. Use para as coisas que a sua classe oferece ao mundo: Guardar, Total, Vender, Abrir.

Cada coisa public é uma promessa. Se outras partes do programa (ou outras pessoas) passaram a usar Vender, mudar o nome ou os parâmetros dele quebra todo esse código. Por isso a regra dos programadores experientes é: comece fechado e só abra o que precisar.


internal: só o mesmo projeto

Um programa maior é dividido em vários projetos (você vai ver em Soluções e Vários Projetos e Bibliotecas de Classes). Cada projeto vira um arquivo compilado, chamado assembly (o .dll). internal quer dizer: visível dentro do mesmo assembly, invisível para os outros.

A Ana separou a lojinha em dois projetos: Estoque, uma biblioteca com as classes dos produtos, e Caixa, o programa que ela roda. No Estoque, o Produto é public e o Fornecedor é internal:

namespace Estoque;

public class Produto
{
    public string Nome = "Caderno";
    public decimal Preco = 12.50m;
}

internal class Fornecedor
{
    public string Nome = "Papelaria Central";
}
using Estoque;

var p = new Produto();
Console.WriteLine($"{p.Nome}: {p.Preco:C}");
var f = new Fornecedor();
$ cd ~/projetos/Caixa
$ dotnet add reference ../Estoque          « o Caixa passa a usar o Estoque
A referência ‘..\Estoque\Estoque.csproj’ foi adicionada ao projeto.
$ dotnet run
/home/ana/projetos/Caixa/Program.cs(5,13): error CS0122: "Fornecedor" é inacessível devido ao seu nível de proteção [/home/ana/projetos/Caixa/Caixa.csproj]

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

O Produto passou, o Fornecedor não. É o mesmo CS0122, agora entre projetos. Apagando a última linha:

$ dotnet run
Caderno: R$ 12,50
E nos programas de um arquivo só? Quando você roda dotnet run cofre.cs, tudo fica no mesmo assembly. Então, ali, internal e public se comportam igual. É por isso que class Cofre, sem modificador (portanto internal), funciona em todos os exemplos desta aula.

protected: a classe e as filhas

Uma classe pode herdar de outra: class Cachorro : Animal quer dizer "um cachorro é um animal e ganha tudo o que o animal tem". Você vai estudar isso com calma em Herança. Por agora, basta a ideia: protected é o armário da família, aberto para a classe e para as classes filhas dela, e fechado para o resto.

var rex = new Cachorro();
rex.Brincar();
rex.Brincar();
Console.WriteLine(rex._energia);

class Animal
{
    protected int _energia = 10;
}

class Cachorro : Animal
{
    public void Brincar()
    {
        _energia -= 3;
        Console.WriteLine($"Rex brincou. Energia: {_energia}");
    }
}
$ dotnet run protegido.cs
/home/ana/cs/protegido.cs(4,23): error CS0122: "Animal._energia" é inacessível devido ao seu nível de proteção

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

Dentro de Cachorro, o método Brincar usa _energia à vontade, porque Cachorro é filho de Animal. A linha 4, que está fora das duas classes, é barrada. Sem ela:

$ dotnet run protegido-ok.cs
Rex brincou. Energia: 7
Rex brincou. Energia: 4

As combinações: protected internal e private protected

O C# ainda tem duas combinações, bem menos usadas. Você raramente vai escrevê-las, mas vai encontrá-las em código pronto:

  • protected internal: aberto para quem é do mesmo projeto ou para as classes filhas (mesmo em outro projeto). É o "ou" das duas.
  • private protected C# 7.2: aberto só para as classes filhas que estão no mesmo projeto. É o "e" das duas.

E existe o file C# 11, que vale só para tipos: file class Ajudante só pode ser usada no próprio arquivo em que foi escrita. Num projeto, outro arquivo que tente usá-la recebe error CS0103: O nome "Ajudante" não existe no contexto atual.


A tabela completa

Do mais aberto para o mais fechado. "Sim" quer dizer que aquele código consegue usar o membro:

ModificadorA própria classeClasse filha, mesmo projetoOutra classe, mesmo projetoClasse filha, outro projetoOutra classe, outro projeto
publicSimSimSimSimSim
protected internalSimSimSimSimNão
internalSimSimSimNãoNão
protectedSimSimNãoSimNão
private protectedSimSimNãoNãoNão
privateSimNãoNãoNãoNão

Modificadores em classes

Uma classe "solta" no arquivo (ou dentro de um namespace) só pode ser public ou internal. Não faz sentido uma classe private lá: ela seria privada de quem? Não há nenhuma classe em volta para ser a "dona".

Console.WriteLine("oi");

private class Segredo
{
}
$ dotnet run topo-private.cs
/home/ana/cs/topo-private.cs(3,15): error CS1527: Os elementos definidos em um namespace não podem ser declarados explicitamente como privados, protegidos, protegidos internamente ou protegidos de forma privada

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

O conserto é tirar o private (a classe fica internal) ou trocá-lo por public. Uma classe dentro de outra pode, sim, ser private: aí a dona é a classe de fora.

Acessibilidade inconsistente

Uma porta aberta não pode levar a uma sala fechada. Se um método é public, os tipos que ele recebe e devolve também têm de ser visíveis para quem chama:

var loja = new Loja();
Console.WriteLine(loja.Achar().Nome);

public class Loja
{
    public Produto Achar()
    {
        return new Produto();
    }
}

class Produto
{
    public string Nome = "Caderno";
}
$ dotnet run inconsistente.cs
/home/ana/cs/inconsistente.cs(6,20): error CS0050: Acessibilidade inconsistente: tipo de retorno "Produto" é menos acessível do que o método "Loja.Achar()"

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

Loja é public, então Achar é visível até para outros projetos. Mas ele devolve um Produto, que é internal (não tem modificador). Outro projeto receberia um objeto de um tipo que ele nem pode enxergar. Há dois consertos: deixar Produto public ou fechar a Loja (tirar o public dela). Com um parâmetro do tipo errado, o erro irmão é o CS0051 ("tipo de parâmetro … é menos acessível").


Qual usar? (regras práticas)

O que éUsePor quê
Campos (os dados guardados)privateNinguém de fora estraga os dados. Para mostrar, use métodos ou propriedades (veja Encapsulamento).
Métodos que a classe oferecepublicSão as portas de entrada.
Métodos ajudantesprivatePodem mudar ou sumir sem quebrar ninguém.
Coisas que as classes filhas precisamprotectedAbre só para a família.
Classes usadas só dentro do projetointernal (ou nada)Outros projetos não dependem delas.
Classes que outros projetos usampublicÉ a "vitrine" da biblioteca.

Na dúvida, comece com private. Abrir depois é fácil: o compilador avisa com CS0122 e você decide. Fechar depois é difícil: alguém pode já estar usando.


Quando dá errado

error CS0122: "…" é inacessível devido ao seu nível de proteção

O que aconteceu: você usou um campo, método ou classe de um lugar que não tem acesso: um membro private de fora da classe, um protected de fora da família, ou um internal de outro projeto.

Como resolver: primeiro, procure a porta aberta que a classe oferece (um método public como Guardar). Se ela não existe e faz sentido existir, crie um método public que faça aquilo com regras. Trocar private por public num campo é o último recurso.

CS0122 num método que você escreveu e "devia funcionar"

O que aconteceu: o método está sem modificador. Dentro de uma classe, isso quer dizer private.

Como resolver: escreva public na frente: public void Abrir(string tentativa).

error CS1527: Os elementos definidos em um namespace não podem ser declarados explicitamente como privados…

O que aconteceu: uma classe solta no arquivo recebeu private, protected ou uma das combinações.

Como resolver: use public ou internal (ou nada, que é internal).

error CS0050 / CS0051: Acessibilidade inconsistente

O que aconteceu: um membro public devolve (CS0050) ou recebe (CS0051) um tipo menos visível que ele.

Como resolver: deixe o tipo tão aberto quanto o método (public class Produto) ou feche o método e a classe dele.


Referência

EscritaSignificado
publicQualquer código usa
privateSó a própria classe usa. Padrão dos membros de classe
internalSó o mesmo projeto (assembly) usa. Padrão das classes soltas
protectedA classe e as filhas
protected internalO mesmo projeto ou as filhas
private protected C# 7.2As filhas que estão no mesmo projeto
file class X C# 11Tipo visível só no próprio arquivo
_nomeConvenção para campos private: _ + camelCase

Missão final: o caixa da lojinha

Missão: escreva a classe Caixa com: (1) um campo private decimal _total; (2) um método public void Vender(decimal valor), que, se valor for menor ou igual a zero, chama Registrar($"Venda recusada: {valor:C}") e para, e senão soma no _total e chama Registrar($"Venda de {valor:C}"); (3) um método public decimal Total(), que devolve o _total; (4) um ajudante private void Registrar(string texto), que mostra [caixa] seguido do texto.

var caixa = new Caixa();
caixa.Vender(8m);
caixa.Vender(0m);
caixa.Vender(5.50m);
Console.WriteLine($"Total do dia: {caixa.Total():C}");

class Caixa
{
    // campo, métodos públicos e o ajudante aqui
}
var caixa = new Caixa();
caixa.Vender(8m);
caixa.Vender(0m);
caixa.Vender(5.50m);
Console.WriteLine($"Total do dia: {caixa.Total():C}");

class Caixa
{
    private decimal _total;

    public void Vender(decimal valor)
    {
        if (valor <= 0)
        {
            Registrar($"Venda recusada: {valor:C}");
            return;
        }
        _total += valor;
        Registrar($"Venda de {valor:C}");
    }

    public decimal Total()
    {
        return _total;
    }

    private void Registrar(string texto)
    {
        Console.WriteLine($"[caixa] {texto}");
    }
}

Exercícios

1. O mais fechado

Qual modificador deixa um campo visível só dentro da própria classe?

2. Sem modificador

Dentro de uma classe, você escreveu int _pontos;, sem modificador. Que modificador o C# aplica?

3. O código do erro

Qual é o código do erro "… é inacessível devido ao seu nível de proteção"? (só o código, como CS0000)

4. Uma classe escrita como class Produto { }, solta no arquivo, é:

5. Um campo protected da classe Animal pode ser usado por:

6. Um método public devolve um tipo internal. O que acontece?


Resumo

  • Modificadores de acesso dizem quem pode usar cada coisa. Quem confere é o compilador, antes de o programa rodar.
  • public: todo mundo. private: só a classe. internal: o mesmo projeto. protected: a classe e as filhas.
  • Sem modificador, membro de classe é private e classe solta é internal. Mesmo assim, escreva sempre o modificador.
  • Campos private (com _nome), métodos que a classe oferece public, ajudantes private. Comece fechado e abra só o necessário.
  • Erros: CS0122 (sem acesso), CS1527 (classe solta private), CS0050/CS0051 (acessibilidade inconsistente).