C# Funções Locais
Uma função local é um método escrito dentro de outro método. Ela só existe ali dentro, e só quem está ali pode chamá-la. Você já escreve funções locais desde a aula Métodos, mesmo sem perceber: os métodos do fim de um arquivo com instruções de nível superior são funções locais do Main escondido. Nesta aula, que fecha o grupo de métodos, você vai usá-las de propósito, dentro dos métodos de uma classe, e entender quando elas ajudam e quando atrapalham.
- O que é uma função local e onde você já usou várias.
- Como escrever uma função local dentro de um método de classe, e por que ela fica invisível fora dele.
- Que a função local enxerga as variáveis e os parâmetros do método que a contém (captura).
- O
staticque proíbe a captura C# 8. - Onde escrever a função local no método, inclusive depois do
return. - Funções locais recursivas e aninhadas.
- A diferença entre uma função local e uma lambda.
- Os erros
CS0103,CS8421,CS0106,CS0128,CS0841e o avisoCS8321.
Todos os programas desta aula rodaram na máquina fictícia da Ana, na pasta ~/cs, com o .NET 10 e o comando dotnet run arquivo.cs. Funções locais existem desde o C# 7.
A ideia: a ferramenta da gaveta da bancada
Na oficina do avô da Ana, as ferramentas que todo mundo usa ficam no painel da parede: qualquer um pega. Mas cada bancada tem uma gaveta com ferramentas que só servem para o trabalho daquela bancada, como o molde específico de um móvel. Ninguém pendura esse molde no painel: ele só atrapalharia quem procura um martelo.
| Na oficina | Em C# |
|---|---|
| Ferramenta do painel, que todos usam | Método da classe: qualquer método da classe pode chamar |
| Ferramenta da gaveta de uma bancada | Função local: só o método que a contém pode chamar |
| Da gaveta, dá para usar o que está em cima da bancada | A função local enxerga as variáveis do método |
1. Você já usa funções locais
Recapitulando Métodos: num arquivo com instruções de nível superior, o compilador embrulha as instruções num Main escondido, e os métodos do fim do arquivo vão para dentro dele. As mensagens do compilador confirmam, como o aviso de método não usado:
$ dotnet run naousado.cs /home/ana/cs/naousado.cs(3,6): warning CS8321: A função local 'Linha' está declarada, mas nunca é usada Bom dia!
Tudo o que esta aula mostra vale para esses métodos também. A novidade é escrever funções locais dentro de qualquer método, inclusive os de uma classe.
2. Uma função local dentro de um método de classe
O método MostrarRecibo desenha três linhas e formata vários preços. Essas duas tarefas só interessam a ele, então viram funções locais, escritas no fim do corpo dele:
class Program
{
static void Main()
{
MostrarRecibo("Ana", 12.50m, 3m, 2.75m);
}
static void MostrarRecibo(string cliente, params decimal[] precos)
{
Linha();
Console.WriteLine($"Cliente: {cliente}");
Linha();
foreach (decimal p in precos)
{
Console.WriteLine(Formatar(p));
}
Linha();
void Linha()
{
Console.WriteLine("--------------------");
}
string Formatar(decimal valor)
{
return $"{valor,12:C}";
}
}
}
$ dotnet run flclasse.cs
--------------------
Cliente: Ana
--------------------
R$ 12,50
R$ 3,00
R$ 2,75
--------------------
- void Linha() { … }
- Uma função local: está dentro das chaves de
MostrarRecibo. Repare que não temstaticnempublic: uma função local é declarada como os métodos do fim do arquivo. - string Formatar(decimal valor)
- Outra função local, com parâmetro e retorno, como qualquer método. O
{valor,12:C}alinha o preço em 12 casas, à direita. - Linha(); no começo do método
- A chamada vem antes da declaração, e funciona: dentro do bloco, a função local pode ser chamada de qualquer linha.
Fora de MostrarRecibo, essas funções não existem. Se o Main tentar usar Linha:
class Program
{
static void Main()
{
MostrarRecibo("Ana");
Linha();
}
static void MostrarRecibo(string cliente)
{
Linha();
Console.WriteLine($"Cliente: {cliente}");
void Linha()
{
Console.WriteLine("--------------------");
}
}
}
$ dotnet run flfora.cs /home/ana/cs/flfora.cs(6,9): error CS0103: O nome "Linha" não existe no contexto atual Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
É a regra das chaves de Escopo: a função mora no bloco de MostrarRecibo. Isso é a vantagem dela, e não um defeito: a classe não fica cheia de métodos pequenos que só um método usa, e quem lê MostrarRecibo encontra os ajudantes ali mesmo. Se outro método precisar da Linha, aí ela deve virar um método da classe (static void Linha(), ao lado do Main).
Missão: o método MostrarResumo repete o mesmo Console.WriteLine três vezes. Crie, dentro de MostrarResumo, uma função local void Linha() que escreve a linha de traços, e troque as três repetições por Linha();. A saída tem de continuar a mesma:
---------- Cliente: Bruno ---------- Total: R$ 15,50 ----------
class Program
{
static void Main()
{
MostrarResumo("Bruno", 15.50m);
}
static void MostrarResumo(string cliente, decimal total)
{
Console.WriteLine("----------");
Console.WriteLine($"Cliente: {cliente}");
Console.WriteLine("----------");
Console.WriteLine($"Total: {total:C}");
Console.WriteLine("----------");
}
}
class Program
{
static void Main()
{
MostrarResumo("Bruno", 15.50m);
}
static void MostrarResumo(string cliente, decimal total)
{
Linha();
Console.WriteLine($"Cliente: {cliente}");
Linha();
Console.WriteLine($"Total: {total:C}");
Linha();
void Linha()
{
Console.WriteLine("----------");
}
}
}
3. A função local enxerga o método
Uma função local enxerga as variáveis e os parâmetros do método que a contém. No exemplo abaixo, Mostrar usa o parâmetro aluno e a variável minima de MostrarNotas sem recebê-los:
class Program
{
static void Main()
{
MostrarNotas("Ana", 7.5, 9, 6);
}
static void MostrarNotas(string aluno, params double[] notas)
{
double minima = 7;
foreach (double n in notas)
{
Mostrar(n);
}
void Mostrar(double nota)
{
string situacao = nota >= minima ? "ok" : "abaixo";
Console.WriteLine($"{aluno}: {nota:F1} ({situacao})");
}
}
}
$ dotnet run flcaptura.cs Ana: 7,5 (ok) Ana: 9,0 (ok) Ana: 6,0 (abaixo)
Isso é a captura, que você viu em Escopo. Ela deixa a função local curta (só recebe o que muda a cada chamada, a nota), mas também esconde do que ela depende: para saber de onde vêm aluno e minima, quem lê precisa olhar o método inteiro.
static: só o que chega por parâmetro C# 8
Com static antes da função local, a captura é proibida. Tudo o que ela usa tem de chegar pelos parâmetros:
class Program
{
static void Main()
{
MostrarNotas("Ana", 7.5, 9, 6);
}
static void MostrarNotas(string aluno, params double[] notas)
{
double minima = 7;
foreach (double n in notas)
{
Console.WriteLine($"{aluno}: {n:F1} ({Situacao(n, minima)})");
}
static string Situacao(double nota, double minimaParaPassar)
{
return nota >= minimaParaPassar ? "ok" : "abaixo";
}
}
}
$ dotnet run flstatic.cs Ana: 7,5 (ok) Ana: 9,0 (ok) Ana: 6,0 (abaixo)
A Situacao agora é uma "função pura": olhando só para ela, você sabe tudo o que ela usa. Se ela tentar usar minima direto, o compilador recusa:
$ dotnet run flstaticerr.cs « static string Situacao(double nota) usando minima /home/ana/cs/flstaticerr.cs(6,20): error CS8421: Uma função local estática não pode conter uma referência a 'minima'. Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
Comece com static nas funções locais. Tire o static só quando a captura deixar o código claramente mais simples, como no contador de vendas de Escopo. Assim, cada captura é uma escolha, e não um acidente.
4. Onde escrever a função local
A função local pode ficar em qualquer ponto do bloco. O costume mais comum é deixá-la no fim do método, depois do código principal, e até depois de um return:
class Program
{
static void Main()
{
Console.WriteLine(Media(new double[] { 8, 6, 10 }));
Console.WriteLine(Media(new double[] { }));
}
static double Media(double[] notas)
{
if (notas.Length == 0)
{
return 0;
}
return Somar() / notas.Length;
double Somar()
{
double total = 0;
foreach (double n in notas) total += n;
return total;
}
}
}
$ dotnet run flvalidar.cs 8 0
Depois de um return, uma linha comum daria o aviso CS0162 (código inacessível, de Valor de Retorno). Uma função local, não: ela não é uma instrução que "roda" naquele ponto, é só uma declaração. Quem lê vê primeiro a lógica do método e, se quiser, desce para os detalhes.
Função local dentro de função local
Uma função local também pode ter as suas. Cada uma só existe dentro de quem a contém:
Relatorio();
void Relatorio()
{
Titulo("Vendas");
Titulo("Estoque");
void Titulo(string texto)
{
Console.WriteLine($"== {texto.ToUpper()} ==");
}
}
$ dotnet run flaninhada2.cs == VENDAS == == ESTOQUE ==
Aqui, Relatorio é uma função local do Main escondido, e Titulo é uma função local de Relatorio. Se a última linha do arquivo chamasse Titulo("Fora"), viria o CS0103: o programa principal não enxerga dentro de Relatorio. Na prática, mais de um nível assim raramente ajuda.
Função local recursiva
Funções locais podem chamar a si mesmas (Recursão). Um uso clássico: o método da classe recebe os dados e prepara a primeira chamada, e a função local recursiva faz o trabalho. Esta busca procura um número num array ordenado, olhando sempre o meio e descartando a metade em que ele não pode estar (a busca binária):
class Program
{
static void Main()
{
int[] numeros = { 4, 8, 15, 16, 23, 42 };
Console.WriteLine(Contem(numeros, 23));
Console.WriteLine(Contem(numeros, 5));
}
static bool Contem(int[] ordenados, int procurado)
{
return Buscar(0, ordenados.Length - 1);
bool Buscar(int inicio, int fim)
{
if (inicio > fim) return false;
int meio = (inicio + fim) / 2;
if (ordenados[meio] == procurado) return true;
if (ordenados[meio] < procurado) return Buscar(meio + 1, fim);
return Buscar(inicio, meio - 1);
}
}
}
$ dotnet run flrecursiva.cs True False
Quem chama Contem(numeros, 23) não precisa saber nada de inicio e fim: esses detalhes ficam escondidos na função local, que captura ordenados e procurado. O caso base é inicio > fim (não sobrou nada para procurar).
Formas diferentes: função local × lambda × método da classe
Existe outro jeito de criar uma "função dentro do método": guardar uma lambda numa variável (aulas Lambdas e Func e Action). As duas linhas abaixo fazem coisas parecidas:
int Dobro(int n) => n * 2; Func<int, int> triplo = n => n * 3; Console.WriteLine(Dobro(5)); Console.WriteLine(triplo(5));
$ dotnet run fllambda.cs 10 15
A primeira é uma função local. A segunda é uma variável chamada triplo, que guarda uma função. Por ser variável, ela segue as regras das variáveis, e não pode ser usada antes da linha em que nasce:
$ dotnet run fllambdaantes.cs « as duas chamadas no topo, as declarações embaixo /home/ana/cs/fllambdaantes.cs(2,19): error CS0841: Não é possível usar a variável local "triplo" antes de declará-la Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
O Dobro(5) da linha 1 não deu erro: função local pode ser chamada antes de ser declarada. Só a lambda reclamou.
| Situação | Use |
|---|---|
| Um ajudante que só um método usa | Função local (de preferência static) |
| Vários métodos da classe precisam dele | Método da classe (static ao lado do Main) |
Passar a função como argumento para outro método (lista.Where(x => x > 5)) | Lambda (Lambdas) |
| Recursão com parâmetros extras que quem chama não deve ver | Método da classe + função local recursiva |
| Duas versões com o mesmo nome | Método da classe: funções locais não aceitam sobrecarga (CS0128) |
Quando dá errado
error CS0103: O nome "Linha" não existe no contexto atual
O que significa: você chamou uma função local de fora do método em que ela foi declarada.
Como consertar: se outros métodos também precisam dela, transforme-a num método da classe (static void Linha(), fora de qualquer método). Se não, chame-a só de dentro.
error CS8421: Uma função local estática não pode conter uma referência a 'minima'.
O que significa: a função local é static e tentou usar uma variável (ou um parâmetro) do método de fora.
Como consertar: acrescente um parâmetro e passe o valor na chamada. Tire o static só se a captura for mesmo o que você quer.
error CS0106: O modificador "public" não é válido para este item
$ dotnet run flpublic.cs « public void Linha() no fim do arquivo /home/ana/cs/flpublic.cs(3,1): error CS0106: O modificador "public" não é válido para este item Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
O que significa: uma função local não pode ter public, private nem internal: ela é sempre visível só dentro do bloco, então essas palavras não fazem sentido. Acontece muito ao copiar um método de uma classe para o fim de um arquivo com instruções de nível superior.
Como consertar: apague o modificador. O static pode ficar: ele é o único que uma função local aceita (além de alguns avançados, como o async, que você vai ver bem mais adiante).
error CS0128: Uma variável de local ou função denominada 'Dobro' já está definida neste escopo
O que significa: duas funções locais com o mesmo nome no mesmo bloco. Funções locais não podem ser sobrecarregadas.
Como consertar: use nomes diferentes ou leve as versões para a classe (Sobrecarga).
error CS0841: Não é possível usar a variável local "triplo" antes de declará-la
O que significa: você usou uma lambda guardada numa variável antes da linha que a cria. Funções locais podem ser chamadas antes; variáveis, não.
Como consertar: mova a declaração para cima, ou transforme a lambda numa função local (int Triplo(int n) => n * 3;).
warning CS8321: A função local 'Linha' está declarada, mas nunca é usada
O que significa: ninguém chama a função. Em geral é um nome digitado errado na chamada, ou um ajudante que sobrou de uma versão antiga.
Como consertar: chame-a onde deveria rodar, ou apague-a.
Referência rápida
| Escrita / regra | O que faz |
|---|---|
void Ajudar() { … } dentro de um método | Função local: só esse método enxerga e chama |
| Métodos no fim de um arquivo com instruções de nível superior | São funções locais do Main escondido |
| Captura | A função local usa (e muda) variáveis e parâmetros do método de fora |
static void Ajudar(…) dentro de um método | Função local sem captura: só usa o que recebe (CS8421 se tentar) |
| Posição | Qualquer lugar do bloco, até depois do return; pode ser chamada antes de declarada |
| Não pode | Ter public/private (CS0106), ser sobrecarregada (CS0128) |
| Pode | Ter parâmetros, retorno, =>, ser recursiva, ter funções locais dentro |
Exercícios
1. A palavra que proíbe a captura
Complete a declaração da função local para que ela não possa usar variáveis do método de fora:
2. O código do erro
Qual é o código do erro que aparece quando uma função local tem public? (por exemplo, CS1234)
3. Quanto mostra?
Dentro de um método: int bonus = 3; Console.WriteLine(Somar(10)); e, no fim do método, int Somar(int x) => x + bonus;. O que aparece?
4. A função local Linha foi declarada dentro de MostrarRecibo. Quem pode chamá-la?
5. Uma função local declarada depois de um return…
6. Qual pode ser usada antes da linha em que é declarada?
Missão final: a taxa de entrega
Missão: a função local ComTaxa é static, mas usa a variável taxa do método, e o programa não compila (CS8421). Mantendo o static, acrescente um segundo parâmetro decimal para a taxa e passe-a na chamada. A saída deve ser:
R$ 10,00 -> R$ 11,00 R$ 25,00 -> R$ 27,50 R$ 40,00 -> R$ 44,00
class Program
{
static void Main()
{
MostrarPrecos(10m, 25m, 40m);
}
static void MostrarPrecos(params decimal[] precos)
{
decimal taxa = 0.10m;
foreach (decimal p in precos)
{
Console.WriteLine($"{p:C} -> {ComTaxa(p):C}");
}
static decimal ComTaxa(decimal preco)
{
return preco + preco * taxa;
}
}
}
class Program
{
static void Main()
{
MostrarPrecos(10m, 25m, 40m);
}
static void MostrarPrecos(params decimal[] precos)
{
decimal taxa = 0.10m;
foreach (decimal p in precos)
{
Console.WriteLine($"{p:C} -> {ComTaxa(p, taxa):C}");
}
static decimal ComTaxa(decimal preco, decimal taxa)
{
return preco + preco * taxa;
}
}
}
Repare que o parâmetro da função local também pode se chamar taxa: dentro dela, o nome é o do parâmetro, e não há captura nenhuma.
Resumo
- Uma função local é um método declarado dentro de outro. Só o método que a contém pode chamá-la (
CS0103fora dele). - Os métodos do fim de um arquivo com instruções de nível superior já são funções locais do
Mainescondido. - A função local enxerga e muda as variáveis e os parâmetros do método de fora (captura). Com
static, isso é proibido (CS8421), e ela só usa o que recebe. - Pode ficar em qualquer ponto do bloco, até depois do
return, e ser chamada antes de declarada. Pode ser recursiva e ter funções locais dentro. - Não aceita
public/private(CS0106) nem sobrecarga (CS0128). Diferente de uma lambda guardada em variável, pode ser usada antes da declaração.