CodeGym /Cursos /C# SELF /Funções de ordem superior

Funções de ordem superior

C# SELF
Nível 51, Lição 2
Disponível

1. Introdução

Muito provavelmente na escola já te falaram: "É uma função de uma função". Funções de ordem superior (Higher-Order Functions, HOF) são funções que aceitam outras funções como argumentos, ou retornam funções como resultado, ou fazem as duas coisas.

Falando de forma simples: se seu método pode receber outra função como parâmetro (por exemplo, um delegate ou uma lambda), ou retornar uma função como resultado — parabéns, você tem uma função de ordem superior!

Uma função de ordem superior é como uma chave mestra que não só abre uma porta por si só, como também pode te passar outra chave para abrir outra porta mais tarde.

Aplicação na vida real

Tudo isso soa legal e meio abstrato. Mas por que esses truques são úteis no dia a dia de um dev C#? A resposta curta:
Funções de ordem superior deixam o código flexível, reutilizável e conciso.
É a base de coisas como LINQ, filtragem e ordenação de coleções, construção de pipelines de processamento, configuração de callbacks, eventos e até injeção de dependências.

Alguns cenários reais:

  • Limitar o comportamento de um método passando a lógica (por exemplo, filtragem, ordenação, transformação).
  • Escrever um handler genérico de dados, no qual você "screw in" a operação necessária via delegate.
  • Construir cadeias de processamento ("pipelines"), onde cada função modifica os dados segundo sua receita.
  • Criar abstrações cross-platform: o que fazer no Windows, o que fazer no Linux? Só passa a função certa.

2. Exemplos de funções de ordem superior simples

Função que recebe outra função

O exemplo clássico — um método que recebe um delegate ou uma lambda.


// Função de ordem superior: recebe a função process como parâmetro
void ForEach<T>(IEnumerable<T> collection, Action<T> process)
{
    foreach (var item in collection)
    {
        process(item); // Chamamos a função-argumento
    }
}

// Uso:
var numbers = new List<int> { 1, 2, 3 };
ForEach(numbers, n => Console.WriteLine($"Elemento: {n}"));

O que acontece aqui? O método ForEach não sabe o que exatamente fazer com cada elemento. Tudo o que ele faz é chamar o processador passado (process). Esse processador pode ser qualquer coisa — imprimir na tela, salvar no banco, desenhar algo, etc.

Sim, é assim que o método ForEach das coleções em C# funciona, e praticamente todos os métodos do LINQ são funções de ordem superior!

Função que retorna uma função

Agora um exemplo um pouco mais forte — um método que não só recebe, mas também retorna uma função.


// Função de ordem superior: retorna outra função
Func<int, int> CreateMultiplier(int factor)
{
    // Retornamos uma lambda que usa a variável factor
    return x => x * factor;
}

// Uso:
var multiplyBy10 = CreateMultiplier(10);
Console.WriteLine(multiplyBy10(7)); // 70

Aqui CreateMultiplier retorna uma função que multiplica seu argumento por um fator predefinido. Isso já é uma "fábrica de funções".

Função que recebe e retorna função


Func<int, int> Compose(Func<int, int> f, Func<int, int> g)
{
    // Vai retornar uma função que aplica g e depois f: f(g(x))
    return x => f(g(x));
}

// Uso:
Func<int, int> increment = x => x + 1;
Func<int, int> doubleIt = x => x * 2;

var incrementThenDouble = Compose(doubleIt, increment);

Console.WriteLine(incrementThenDouble(5)); // (5 + 1) * 2 = 12

Exatamente essas composições são a base do processamento de dados em fluxo — por exemplo, quando você encadeia métodos Select, Where, OrderBy etc.

3. Como C# dá suporte a funções de ordem superior

Em linguagens funcionais (Haskell, F#) todas as funções são de ordem superior por padrão. Mas o C# (a partir da versão 2.0) também suporta isso via delegates e lambdas.

  • Delegates (Func, Action, Predicate) — tipos de função.
  • Expressões lambda — sintaxe para criar funções inline.
  • Métodos podem aceitar e retornar delegates — então funções de ordem superior são suportadas "out of the box".

Esquema visual

flowchart LR
    A[Dados] --> B[Função 1]
    B --> C[Função 2]
    C --> D[Resultado]
    subgraph "Pipeline de processamento (funções de ordem superior)"
        B
        C
    end

4. Desenvolvendo nosso aplicativo

Vamos continuar desenvolvendo nosso demo passo a passo — vamos criar um "Mini-processador de strings".

Adicionando um método simples de ordem superior

Imagine que temos uma lista de nomes de usuários e queremos modificar cada um arbitrariamente usando funções.


// Método que recebe uma lista de strings e uma função para transformar
List<string> TransformNames(List<string> names, Func<string, string> transformer)
{
    var result = new List<string>();
    foreach (var name in names)
    {
        result.Add(transformer(name));
    }
    return result;
}

Como usar esse método?


var names = new List<string> { "Anna", "Boris", "Sergey" };

// Converter para maiúsculas
var upperNames = TransformNames(names, n => n.ToUpper());

// Adicionar "Prezado(a) " a cada nome
var politeNames = TransformNames(names, n => "Prezado(a) " + n);

foreach (var n in upperNames)
    Console.WriteLine(n); // ANNA, BORIS, SERGEY

foreach (var n in politeNames)
    Console.WriteLine(n); // Prezado(a) Anna, ...

O método TransformNames é genérico: ele delega a lógica de transformação ao parâmetro (transformer), por exemplo chamando ToUpper ou qualquer outra receita.

Adaptação a tipos

Esse exemplo é fácil de generalizar para qualquer tipo de dado.


// Método genérico - função de ordem superior que trabalha com qualquer T
List<TResult> Map<T, TResult>(List<T> items, Func<T, TResult> transformer)
{
    var result = new List<TResult>();
    foreach (var item in items)
    {
        result.Add(transformer(item));
    }
    return result;
}

Aplicação:


var numbers = new List<int> { 1, 2, 3 };
var doubled = Map(numbers, x => x * 2); // [2, 4, 6]
var strings = Map(numbers, x => $"Número: {x}"); // ["Número: 1", ...]

5. Filtragem e agregação via funções de ordem superior

A lógica de filtragem e busca é frequentemente implementada com funções de ordem superior.


// Filtragem: função de ordem superior
List<T> Filter<T>(List<T> items, Predicate<T> criteria)
{
    var result = new List<T>();
    foreach (var item in items)
    {
        if (criteria(item)) // Chamamos a função-critério
        {
            result.Add(item);
        }
    }
    return result;
}

Como usar:


var names = new List<string> { "Anna", "Boris", "Andrey" };
var aNames = Filter(names, n => n.StartsWith("A"));
// Resultado: "Anna", "Andrey"

6. Conceito de composição de funções (function composition)

Funções de ordem superior não servem só para usar funções isoladas, mas também para montar cadeias — as chamadas composições. Em C# isso pode ser implementado como uma função que recebe duas funções e retorna a nova combinação delas.


// Compositor de funções: retorna uma função que aplica primeiro f, depois g: f(g(x))
Func<T, TResult> Compose<T, TIntermediate, TResult>(
    Func<TIntermediate, TResult> f,
    Func<T, TIntermediate> g)
{
    return x => f(g(x));
}

// Exemplo:
Func<int, int> plusOne = n => n + 1;
Func<int, int> timesTwo = n => n * 2;

var plusOneThenDouble = Compose(timesTwo, plusOne);
Console.WriteLine(plusOneThenDouble(3)); // (3 + 1) * 2 = 8

7. Dicas úteis

Contexto histórico: por que antes tudo era mais difícil?

Antes de delegates e lambdas os devs costumavam escrever muitos loops iguais, copiando trechos de código para "filtrar", "transformar" ou "agrupar" dados. Com a chegada das funções de ordem superior ficou possível extrair a parte variável do comportamento para funções-parametro — reduzindo duplicação e deixando o código muito mais expressivo.

Um pouco de syntactic sugar: funções como expressões

Funções de ordem superior costumam aparecer com métodos expression-bodied — métodos de uma linha bem concisos:


List<string> FilterNames(Predicate<string> pred) =>
    Names.Where(name => pred(name)).ToList();

List<TResult> MapNames<TResult>(Func<string, TResult> transformer) =>
    Names.Select(transformer).ToList();

Comparação com os mecanismos do LINQ

Vamos ver como o LINQ usa funções de ordem superior:

Método LINQ Qual delegado recebe Finalidade
Where
Func<T, bool>
Filtra elementos
Select
Func<T, TResult>
Transforma elementos
OrderBy
Func<T, TKey>
Ordena pela chave
Aggregate
Func<TAcc, T, TAcc>
Agrega (reduz) a coleção
Any
Func<T, bool>
Verifica existência de elemento por condição

Todos esses métodos se apoiam na ideia de funções de ordem superior: você escreve as regras, a biblioteca padrão fornece a infraestrutura.

8. Possíveis erros e armadilhas

Confusão com tipos de delegates.
No começo pode ser difícil entender quando usar Action, quando Func e quando Predicate.
Dica: se a função retorna bool — provavelmente é Predicate. Se retorna um valor — use Func, se não retorna nada — Action.

Captura de variáveis (closures).
Se a função retornada usa variáveis do contexto externo, preste atenção para que os valores estejam corretos no momento da chamada. Variáveis não são copiadas, elas são "capturadas".

Depuração de cadeias complexas.
Quando funções são compostas em longos pipelines, fica difícil descobrir qual camada mexeu nos dados de forma errada. Insira logs temporários e comentários:


n => {
  Console.WriteLine("Antes da operação:" + n);
  var res = n * 2;
  Console.WriteLine("Depois:" + res);
  return res;
}
2
Tarefa
C# SELF, nível 51, lição 2
Bloqueado
Filtragem usando funções de ordem superior
Filtragem usando funções de ordem superior
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION