Функциональное программирование в C#: принципы и примеры

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Функциональное программирование в C#: от теории к практике

Функциональное программирование (ФП) — это парадигма, в которой вычисления рассматриваются как вычисление математических функций, избегая изменения состояния и изменяемых данных. Хотя C# изначально был объектно-ориентированным языком, начиная с версии 3.0 он активно впитывает функциональные возможности. Сегодня C# поддерживает такие концепции, как лямбда-выражения, LINQ, неизменяемые типы и даже pattern matching. В этой статье мы разберем ключевые принципы ФП и покажем, как их применять в реальных проектах на C#.

1. Чистые функции и отсутствие побочных эффектов

Чистая функция — это функция, которая:

  • Всегда возвращает один и тот же результат для одних и тех же аргументов (ссылочная прозрачность).
  • Не имеет побочных эффектов (не изменяет глобальное состояние, не пишет в файлы, не отправляет HTTP-запросы).

В C# чистая функция может выглядеть так:

public static int Add(int a, int b) => a + b;

А вот пример нечистой функции:

public static int AddAndLog(int a, int b)
{
    Console.WriteLine($"Складываем {a} и {b}"); // побочный эффект
    return a + b;
}

Преимущества чистых функций: их легко тестировать, отлаживать и параллелить.

2. Неизменяемость (Immutability)

В ФП данные по умолчанию неизменяемы. В C# для этого используются:

  • Ключевое слово readonly для полей.
  • Структуры record (C# 9+), которые автоматически создают неизменяемые объекты.
  • Коллекции из пространства имен System.Collections.Immutable.

Пример с record:

public record Person(string Name, int Age);

Если нужно изменить возраст, создается новый экземпляр:

Person person = new("Иван", 30);
Person olderPerson = person with { Age = 31 };

Неизменяемость предотвращает случайные мутации и упрощает многопоточность.

3. Функции высшего порядка и лямбда-выражения

Функции высшего порядка принимают другие функции как аргументы или возвращают их. В C# это реализуется через делегаты: Func<>, Action<>, Predicate<>.

Простой пример — метод Where из LINQ:

List<int> numbers = new() { 1, 2, 3, 4, 5 };
var evenNumbers = numbers.Where(n => n % 2 == 0).ToList();

Здесь n => n % 2 == 0 — это лямбда-выражение (анонимная функция).

Можно написать свою функцию высшего порядка:

public static List<T> Filter<T>(List<T> list, Func<T, bool> predicate)
{
    var result = new List<T>();
    foreach (var item in list)
        if (predicate(item)) result.Add(item);
    return result;
}

4. LINQ: мощный инструмент функционального стиля

LINQ (Language Integrated Query) — это, пожалуй, самый яркий пример функционального программирования в C#. Он позволяет обрабатывать коллекции декларативно, как в SQL.

Пример: получить имена пользователей старше 18 лет, отсортированные по возрасту:

var users = new List<User>
{
    new("Анна", 22),
    new("Борис", 17),
    new("Виктор", 30)
};

var result = users
    .Where(u => u.Age >= 18)
    .OrderBy(u => u.Age)
    .Select(u => u.Name)
    .ToList();

LINQ поддерживает отложенное выполнение (lazy evaluation), что экономит ресурсы.

5. Монады и Maybe-тип в C#

Монады — это абстракция для цепочек вычислений с контекстом (например, обработка null или асинхронность). В C# нет встроенной монады Maybe, но её легко реализовать:

public class Maybe<T>
{
    private readonly T _value;
    public bool HasValue { get; }
    
    private Maybe(T value, bool hasValue) => (_value, HasValue) = (value, hasValue);
    
    public static Maybe<T> Some(T value) => new(value, true);
    public static Maybe<T> None() => new(default!, false);
    
    public Maybe<TResult> Bind<TResult>(Func<T, Maybe<TResult>> func) =>
        HasValue ? func(_value) : Maybe<TResult>.None();
}

Использование:

Maybe<int> GetUserAge(string userId) { /* ... */ }
Maybe<string> GetDiscount(int age) { /* ... */ }

var discount = GetUserAge("123")
    .Bind(age => GetDiscount(age));

Такой подход заменяет бесконечные проверки на null и делает код более предсказуемым.

6. Pattern Matching (сопоставление с образцом)

Начиная с C# 7, появился мощный механизм сопоставления с образцом. Он идеально вписывается в функциональный стиль:

public static string Describe(object obj) => obj switch
{
    int i when i > 0 => "Положительное целое",
    int i when i < 0 => "Отрицательное целое",
    string s => $"Строка длиной {s.Length}",
    null => "null",
    _ => "Другой тип"
};

Это избавляет от громоздких конструкций if-else и делает код более читаемым.

Заключение

Функциональное программирование в C# — это не просто модный тренд, а мощный инструмент для написания надёжного, тестируемого и параллельного кода. Даже если вы не планируете полностью переходить на ФП, внедрение отдельных принципов (чистые функции, неизменяемость, LINQ) значительно улучшит качество ваших проектов. Начните с малого: замените циклы на LINQ, использу

Блоги

Рекомендаций по книгам