Функциональный vs ООП в C#: что выбрать в 2025?

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

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

Начать курс

Введение: две стороны одной монеты

Представьте, что вы строите дом. Можно взять готовые блоки (классы, объекты, наследование) и собрать из них сложную конструкцию — это объектно-ориентированное программирование (ООП). А можно мыслить как математик: "я беру данные, применяю к ним чистую функцию и получаю новый результат, ничего не меняя" — это функциональное программирование.

В C# удивительным образом уживаются обе парадигмы. И если вы пишете на этом языке, рано или поздно зададитесь вопросом: "А что, если я делаю это неправильно? Может, стоит больше использовать LINQ и меньше классов?"

В этой статье мы не будем спорить, что лучше. Мы разберёмся, чем функциональный подход отличается от ООП в C#, когда каждый из них реально помогает, а когда — вредит. И да, будет много кода. Поехали!

1. ООП в C#: классика, которая работает

Объектно-ориентированное программирование — это про состояние и поведение. У вас есть класс, который хранит данные (поля, свойства) и методы, которые эти данные изменяют.

Пример: приложение для управления заказами в интернет-магазине.

public class Order
{
    public int Id { get; set; }
    public List<string> Items { get; private set; }
    public decimal TotalPrice { get; private set; }

    public Order(int id)
    {
        Id = id;
        Items = new List<string>();
        TotalPrice = 0;
    }

    public void AddItem(string itemName, decimal price)
    {
        Items.Add(itemName);
        TotalPrice += price;
    }

    public void ApplyDiscount(decimal percent)
    {
        TotalPrice -= TotalPrice * (percent / 100);
    }
}

Что мы видим? Инкапсуляция: TotalPrice нельзя изменить напрямую — только через методы. Состояние объекта меняется со временем: мы добавляем товары, применяем скидки. Это чистое ООП.

Плюсы ООП в C#

  • Понятная модель реального мира: Заказ, Клиент, Товар — всё как в жизни.
  • Повторное использование через наследование и интерфейсы.
  • Управление сложностью за счёт разбиения на маленькие объекты.

Минусы ООП

  • Побочные эффекты: один метод может менять состояние, и вы легко теряете контроль.
  • Сложность тестирования: нужно создавать объекты, мокать зависимости.
  • Избыточность: иногда проще написать функцию, чем создавать целый класс.

2. Функциональный подход в C#: LINQ и не только

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

Давайте решим ту же задачу — управление заказом — но функционально.

// Неизменяемая запись (record)
public record Order(int Id, List<string> Items, decimal TotalPrice);

public static class OrderFunctions
{
    public static Order AddItem(Order order, string item, decimal price)
    {
        var newItems = order.Items.Append(item).ToList();
        return order with { Items = newItems, TotalPrice = order.TotalPrice + price };
    }

    public static Order ApplyDiscount(Order order, decimal percent)
    {
        return order with { TotalPrice = order.TotalPrice * (1 - percent / 100) };
    }
}

// Использование
var order = new Order(1, new List<string>(), 0);
order = OrderFunctions.AddItem(order, "Ноутбук", 50000);
order = OrderFunctions.ApplyDiscount(order, 10);

Заметили? Мы не меняем исходный объект. Каждый раз создаётся новый экземпляр с помощью with. Это исключает случайные изменения.

А теперь — магия LINQ. Представьте, что у вас есть список заказов, и нужно найти все, где сумма больше 1000:

var expensiveOrders = orders
    .Where(o => o.TotalPrice > 1000)
    .OrderByDescending(o => o.TotalPrice)
    .Select(o => new { o.Id, o.TotalPrice })
    .ToList();

Это чистая функция: orders не изменился, мы просто получили новую коллекцию. Функциональный стиль C# делает код декларативным — вы говорите "что" сделать, а не "как".

Плюсы функционального подхода

  • Предсказуемость: функция всегда возвращает одно и то же для одних и тех же входных данных.
  • Лёгкое тестирование: не нужно мокать объекты, просто передал данные — проверил результат.
  • Параллелизм: с неизменяемыми данными нет блокировок и гонок.

Минусы функционального подхода

  • Производительность: создание новых объектов — это затраты памяти. Для больших данных может быть проблемой.
  • Крутая кривая обучения: не всем легко привыкнуть к рекурсии и отсутствию циклов.
  • Не всегда уместно: для простых CRUD-приложений функциональный стиль избыточен.

3. Гибридный подход: лучшее из двух миров

Современный C# — это мультипарадигменный язык. Microsoft явно поощряет гибридный стиль, когда вы используете ООП для архитектуры, а функциональные приёмы — внутри методов.

Вот типичный пример из реального проекта:

public class UserService
{
    private readonly IUserRepository _repository;

    public UserService(IUserRepository repository) // DI — это ООП
    {
        _repository = repository;
    }

    public async Task<List<UserDto>> GetActiveUsersAsync()
    {
        var users = await _repository.GetAllAsync();
        
        // Чисто функциональная обработка
        return users
            .Where(u => u.IsActive)
            .Select(u => new UserDto
            {
                Id = u.Id,
                FullName = $"{u.FirstName} {u.LastName}",
                Age = CalculateAge(u.BirthDate)
            })
            .OrderBy(u => u.FullName)
            .ToList();
    }

    // Чистая функция
    private int CalculateAge(DateTime birthDate) 
        => DateTime.Now.Year - birthDate.Year - 
           (DateTime.Now.DayOfYear < birthDate.DayOfYear ? 1 : 0);
}

Что здесь происходит?

  • ООП: класс UserService, внедрение зависимости, работа с репозиторием.
  • Функциональный стиль: LINQ-цепочки, иммутабельные коллекции, чистая функция CalculateAge.

Такой подход — золотая середина. Вы не пи

Блоги

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