Programação Funcional em C#: Técnicas e Padrões Avançados

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Programação Funcional em C#: da Sintaxe à Arquitetura

\n\n

O C# moderno há muito ultrapassou os limites de uma linguagem puramente orientada a objetos. A partir da versão 3.0 (LINQ) e até os lançamentos mais recentes do .NET, a linguagem absorve ativamente os paradigmas da programação funcional (PF). Se você já domina lambdas e LINQ, mas quer se aprofundar, este artigo é para você. Vamos explorar conceitos avançados de PF que tornarão seu código C# mais previsível, testável e conciso.

\n\n

1. Funções Puras e Trabalho com Efeitos Colaterais

\n\n

Função pura é uma função que:

\n
    \n
  • Sempre retorna o mesmo resultado para os mesmos argumentos (transparência referencial).
  • \n
  • Não possui efeitos colaterais (não altera estado global, não escreve em arquivo, não envia requisições HTTP).
  • \n
\n\n

Em C#, o exemplo ideal de função pura são operações matemáticas. O problema é que uma aplicação real não pode consistir apenas de funções puras: mais cedo ou mais tarde, é necessário salvar dados ou enviar um e-mail. A solução é separar a lógica dos efeitos.

\n\n

Técnica avançada: use functors e mônadas para encapsular efeitos. Vamos considerar um exemplo simples com Maybe<T> (análogo ao nullable, mas funcional):

\n\n
public class Maybe<T>\n{\n    private readonly T _value;\n    public bool HasValue { get; }\n    \n    private Maybe(T value, bool hasValue) => (_value, HasValue) = (value, hasValue);\n    \n    public static Maybe<T> Some(T value) => new Maybe<T>(value, true);\n    public static Maybe<T> None() => new Maybe<T>(default, false);\n    \n    // Functor: Select (Map)\n    public Maybe<TResult> Select<TResult>(Func<T, TResult> selector) =>\n        HasValue ? Maybe<TResult>.Some(selector(_value)) : Maybe<TResult>.None();\n    \n    // Mônada: SelectMany (FlatMap/Bind)\n    public Maybe<TResult> SelectMany<TResult>(Func<T, Maybe<TResult>> binder) =>\n        HasValue ? binder(_value) : Maybe<TResult>.None();\n}\n\n// Uso\nMaybe<int> result = Maybe<int>.Some(10)\n    .Select(x => x * 2)\n    .SelectMany(x => x > 15 ? Maybe<int>.Some(x) : Maybe<int>.None());
\n\n

Este código não gera exceções nem verifica null manualmente — toda a lógica de tratamento de ausência de valor está encapsulada na mônada.

\n\n

2. Currying e Aplicação Parcial

\n\n

Na PF clássica, cada função recebe um único argumento. Currying é a transformação de uma função com vários parâmetros em uma cadeia de funções aninhadas, cada uma recebendo um argumento. Aplicação parcial é a fixação de parte dos argumentos.

\n\n

Em C#, não há currying nativo, mas é fácil implementá-lo usando closures:

\n\n
// Função comum\nFunc<int, int, int, int> add = (a, b, c) => a + b + c;\n\n// Versão curried\nFunc<int, Func<int, Func<int, int>>> curriedAdd = a => b => c => a + b + c;\n\n// Aplicação parcial: fixamos o primeiro argumento\nFunc<int, Func<int, int>> add5 = curriedAdd(5);\n\n// Uso\nint sum = add5(10)(20); // 35
\n\n

Cenário avançado: injeção de dependências via aplicação parcial. Em vez de passar configuração ou serviço pelo construtor da classe, você pode fixá-los na etapa de composição:

\n\n
public class ReportService\n{\n    public Func<string, string, string> FormatReport => \n        (template, data) => $"[{DateTime.Now}] {template}: {data}";\n}\n\n// Uso com aplicação parcial\nvar service = new ReportService();\nvar formatWithTemplate = service.FormatReport("Error"); // fixamos o template\nstring errorMessage = formatWithTemplate("Connection failed");                

Blogs

Book Recommendations