C#: Método Avançado
1. Sobrecarga de métodos
A sobrecarga de métodos permite definir vários métodos com o mesmo nome na mesma classe, desde que suas listas de parâmetros sejam diferentes. Uma lista de parâmetros diferente significa um número, tipo ou ordem de parâmetros diferente. O tipo de retorno não influencia na resolução da sobrecarga.
▶ Exemplo
using System;
class Calculator
{
public int Add(int a, int b)
{
return a + b;
}
public double Add(double a, double b)
{
return a + b;
}
public int Add(int a, int b, int c)
{
return a + b + c;
}
}
class Program
{
static void Main()
{
Calculator calc = new Calculator();
Console.WriteLine(calc.Add(1, 2));
Console.WriteLine(calc.Add(1.5, 2.5));
Console.WriteLine(calc.Add(1, 2, 3));
}
}
3
4
6
2. Parâmetros opcionais e nomeados
Os parâmetros opcionais atribuem valores padrão aos parâmetros na declaração do método; esses parâmetros podem ser omitidos ao chamar o método. Os parâmetros nomeados passam valores especificando os nomes dos parâmetros, sem a necessidade de seguir a ordem posicional.
▶ Exemplo
using System;
class Program
{
static void Print(string msg, int count = 1, string prefix = "")
{
for (int i = 0; i < count; i++)
{
Console.WriteLine($"{prefix}{msg}");
}
}
static void Main()
{
Print("Hello");
Print("Hi", 3);
Print(msg: "World", prefix: "> ");
Print(prefix: ">> ", count: 2, msg: "Test");
}
}
Hello
Hi
Hi
Hi
> World
>> Test
>> Test
3. O modificador de parâmetro ref
A palavra-chave ref passa um parâmetro por referência — as modificações feitas no parâmetro dentro do método afetam a variável do chamador. A variável deve ser inicializada antes da chamada, e a palavra-chave ref também deve ser usada no local da chamada.
▶ Exemplo
using System;
class Program
{
static void Swap(ref int a, ref int b)
{
int temp = a;
a = b;
b = temp;
}
static void Main()
{
int x = 10;
int y = 20;
Console.WriteLine($"Before: x={x}, y={y}");
Swap(ref x, ref y);
Console.WriteLine($"After: x={x}, y={y}");
}
}
Before: x=10, y=20
After: x=20, y=10
4. O modificador de parâmetro “out”
A palavra-chave out também é passada por referência, mas o método deve atribuir um valor ao parâmetro out, e a variável não precisa ser inicializada antes da chamada. É comumente usada quando um método precisa retornar vários valores.
▶ Exemplo
using System;
class Program
{
static bool TryParseInt(string s, out int result)
{
result = 0;
foreach (char c in s)
{
if (c < '0' || c > '9')
return false;
result = result * 10 + (c - '0');
}
return true;
}
static void Main()
{
if (TryParseInt("123", out int number))
{
Console.WriteLine($"Parsed: {number}");
}
if (!TryParseInt("abc", out int fail))
{
Console.WriteLine("Parse failed");
}
}
}
Parsed: 123
Parse failed
out devem ser atribuídos antes que o método retorne. A partir do C# 7, é possível declarar variáveis diretamente no local da chamada.
5. O modificador de parâmetro “in”
A palavra-chave in passa um parâmetro por referência como somente leitura — o método não pode modificar o valor de um parâmetro in. Isso é útil para passar estruturas grandes, a fim de evitar a sobrecarga da cópia e, ao mesmo tempo, impedir modificações acidentais.
▶ Exemplo
using System;
struct BigData
{
public long A, B, C, D, E, F, G, H;
}
class Program
{
static void Process(in BigData data)
{
Console.WriteLine($"A={data.A}, B={data.B}");
}
static void Main()
{
BigData d = new BigData { A = 100, B = 200 };
Process(in d);
Process(d);
}
}
A=100, B=200
A=100, B=200
ℹ️ Observação: A palavra-chave
inpode ser omitida no local da chamada; o compilador passará automaticamente por referência.
6. Argumentos variáveis params
A palavra-chave params permite que um método aceite um número variável de argumentos — o compilador os agrupa em uma matriz. Um método só pode ter um parâmetro params, e este deve ser o último na lista de parâmetros.
▶ Exemplo
using System;
class Program
{
static int Sum(params int[] numbers)
{
int total = 0;
foreach (int n in numbers)
{
total += n;
}
return total;
}
static void Main()
{
Console.WriteLine(Sum(1, 2, 3));
Console.WriteLine(Sum(10, 20, 30, 40, 50));
Console.WriteLine(Sum());
}
}
6
150
0
params só pode ser usado com matrizes unidimensionais, e um método pode ter, no máximo, um parâmetro params.
7. Princípios da recursão e exemplos clássicos
A recursão é uma técnica de programação na qual um método chama a si mesmo. Toda recursão deve ter uma condição de terminação (caso base); caso contrário, causará um estouro de pilha.
(1) Fatorial
using System;
class Program
{
static long Factorial(int n)
{
if (n <= 1)
return 1;
return n * Factorial(n - 1);
}
static void Main()
{
for (int i = 1; i <= 10; i++)
{
Console.WriteLine($"{i}! = {Factorial(i)}");
}
}
}
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880
10! = 3628800
(2) Sequência de Fibonacci
using System;
class Program
{
static int Fibonacci(int n)
{
if (n <= 0) return 0;
if (n == 1) return 1;
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
static void Main()
{
for (int i = 0; i <= 10; i++)
{
Console.Write($"{Fibonacci(i)} ");
}
Console.WriteLine();
}
}
0 1 1 2 3 5 8 13 21 34 55
8. Funções locais
O C# 7 introduziu as funções locais, permitindo que métodos sejam definidos dentro de outros métodos. As funções locais só podem ser acessadas dentro do método que as contém e são úteis para extrair lógica auxiliar utilizada apenas em um método específico.
▶ Exemplo
using System;
class Program
{
static int[] GetEvenSquares(int[] numbers)
{
bool IsEven(int n) => n % 2 == 0;
int Square(int n) => n * n;
System.Collections.Generic.List```<int>``` result = new System.Collections.Generic.List```<int>```();
foreach (int n in numbers)
{
if (IsEven(n))
{
result.Add(Square(n));
}
}
return result.ToArray();
}
static void Main()
{
int[] data = { 1, 2, 3, 4, 5, 6, 7, 8 };
int[] result = GetEvenSquares(data);
Console.WriteLine(string.Join(", ", result));
}
}
4, 16, 36, 64
9. Âmbito das variáveis
O escopo de uma variável determina onde ela pode ser acessada. Em C#, existem três níveis principais de escopo: escopo de bloco (dentro de blocos if/for/while), escopo de método (variáveis locais definidas dentro de um método) e escopo de classe (campos definidos em uma classe).
▶ Exemplo
using System;
class ScopeDemo
{
int classField = 100;
void MethodA()
{
int localVar = 10;
Console.WriteLine($"classField={classField}, localVar={localVar}");
if (true)
{
int blockVar = 20;
Console.WriteLine($"blockVar={blockVar}");
}
}
void MethodB()
{
Console.WriteLine($"classField={classField}");
}
}
class Program
{
static void Main()
{
ScopeDemo demo = new ScopeDemo();
demo.MethodA();
demo.MethodB();
}
}
classField=100, localVar=10
blockVar=20
classField=100
✅ Regra: Não é permitido declarar variáveis locais com o mesmo nome no mesmo método. Os blocos de código internos podem acessar variáveis externas, mas os blocos externos não podem acessar variáveis de blocos internos.
❓ Perguntas Frequentes
P: A sobrecarga de métodos pode se basear exclusivamente no tipo de retorno? R: Não, o compilador faz a correspondência entre métodos com base nos argumentos no local da chamada; o tipo de retorno não faz parte da assinatura. P: Qual é a principal diferença entre ref e out? R: ref exige inicialização antes da chamada; out exige atribuição dentro do método. ref passa um valor existente que pode ser modificado; out é normalmente usado para retornar resultados adicionais. P: Os argumentos de
paramspodem sernull? R: Sim,paramsé essencialmente um array; passarnullnão cria automaticamente um array vazio, portanto, é necessário verificar se hánulldentro do método. P: A recursão é sempre mais lenta do que a iteração? R: Não necessariamente, mas a recursão tem limites de profundidade da pilha e sobrecarga de chamadas; uma recursão profunda pode levar a uma exceção StackOverflowException. P: Qual é a diferença entre funções locais e expressões lambda? R: As funções locais são métodos propriamente ditos, sem a sobrecarga dos delegados, e suportam genéricos e ref/out; as expressões lambda geram delegados ou árvores de expressão com alocações adicionais.
📖 Resumo
- Sobrecarga de métodos: mesmo nome, parâmetros diferentes; o tipo de retorno não é levado em conta
- Parâmetros opcionais: atribua valores padrão na declaração; devem ser colocados após os parâmetros obrigatórios
- Parâmetros nomeados: passar valores pelo nome do parâmetro, eliminando as restrições posicionais
- ref: passagem por referência; deve ser inicializado antes da chamada
- out: passagem por referência; é necessário atribuir um valor dentro do método; não é necessária inicialização antes da chamada
- em: passagem por referência somente leitura, impede modificações e evita a cópia
- params: número variável de argumentos, compilados como uma matriz; só é permitido um, e ele deve ser o último
- Recursão: um método chama a si mesmo; deve ter uma condição de terminação; preste atenção ao desempenho e ao estouro de pilha
- Funções locais: métodos definidos dentro de outros métodos, um recurso do C# 7+ com menor sobrecarga do que as lambda
- Escopo de variáveis: bloco < método < classe; o escopo interno pode acessar o externo, mas o externo não pode acessar o interno
📝 Exercícios
- Escreva três métodos sobrecarregados de
Multiplyque aceitem, respectivamente, dois inteiros, dois números de precisão dupla e três inteiros, retornando o produto. - Escreva um método
void Log(string msg, string level = "INFO", bool timestamp = true)e chame-o pelo menos três vezes, utilizando parâmetros nomeados em ordens diferentes. - Escreva um método
void Increment(ref int value)que incremente em 1 o número inteiro passado e verifique, na função Main, se a variável foi de fato modificada. - Escreva um método
bool TryDivide(int a, int b, out double result)que retorne false quando b for 0; caso contrário, retorne o resultado da divisão por meio de out. - Escreva um método
double Average(params double[] values)que calcule a média de todos os argumentos, tratando o caso de um array vazio. - Implemente um método recursivo para calcular 1+2+3+...+n e compare o resultado com uma versão iterativa.
- Escreva um método que utilize internamente uma função local para determinar se um número é primo e que retorne todos os números primos de 1 a 50.