TGViewer
Библиотека шарписта | C#, F#, .NET, ASP.NET Библиотека шарписта | C#, F#, .NET, ASP.NET @csharpproglib · 21.7K subscribers
Post #7075 3.09K
🛠 Монады в C#

Слово «монада» звучит так, будто пришло из учебника по высшей математике. Если вы писали на C#, вы уже пользовались монадами. Task<T>, Nullable<T>, IEnumerable<T> и весь LINQ построены на этом паттерне.

Монада в двух словах

Монада это обёртка над значением, которая умеет делать две вещи.

Первая. Оборачивать значение в контейнер. В C# это конструктор или фабричный метод. Например, Task.FromResult(42) оборачивает число 42 в Task<int>.

Вторая. Прокидывать значение через цепочку операций, где каждая операция тоже возвращает обёртку. В функциональном программировании эту операцию называют Bind. В C# она реализована как SelectMany.

Формально монада должна соблюдать три закона: левая единица, правая единица, ассоциативность; но для повседневной работы достаточно понимать саму идею цепочки.

Nullable как монада

Nullable<T> это контейнер, который либо содержит значение, либо пуст. До C# 8 приходилось писать вложенные проверки на null:
int? a = GetValue();
int? b = null;
if (a.HasValue)
{
b = Transform(a.Value);
}


Монадный подход позволяет выстроить цепочку без ручных проверок. Напишем свой Bind для Nullable<T>:
public static T? Bind<T>(this T? source, Func<T, T?> func)
where T : struct
{
return source.HasValue ? func(source.Value) : null;
}


Теперь вместо вложенных if мы получаем цепочку:
int? result = GetValue()
.Bind(x => Multiply(x, 2))
.Bind(x => Add(x, 10));


Если на любом шаге значение окажется null, вся цепочка вернёт null. Никаких проверок вручную.

Task как монада

Task<T> работает по тому же принципу. Метод ContinueWith это грубый аналог Bind, но гораздо удобнее пользоваться async/await.

Компилятор сам выстраивает цепочку продолжений:
var user = await GetUserAsync(id);
var orders = await GetOrdersAsync(user);
var total = await CalculateTotalAsync(orders);


Каждый await разворачивает Task<T>, передаёт значение в следующую операцию и снова заворачивает результат в Task. Это и есть монадная цепочка, просто записанная через синтаксический сахар.

LINQ и SelectMany

LINQ query syntax это буквально монадный синтаксис.

Ключевое слово from вызывает SelectMany под капотом:
var pairs =
from x in new[] { 1, 2, 3 }
from y in new[] { "a", "b" }
select (x, y);


Компилятор превращает это в вызов SelectMany:
var pairs = new[] { 1, 2, 3 }
.SelectMany(
x => new[] { "a", "b" },
(x, y) => (x, y));


SelectMany берёт каждый элемент, применяет функцию, которая возвращает новую коллекцию, и склеивает всё в один плоский результат. Это и есть Bind для IEnumerable<T>.

Свой тип Result как монада

В реальных проектах монады полезны для обработки ошибок без исключений.

Напишем простой Result<T>:
public class Result<T>
{
public T Value { get; }
public string Error { get; }
public bool IsSuccess { get; }

private Result(T value)
{
Value = value;
IsSuccess = true;
}

private Result(string error)
{
Error = error;
IsSuccess = false;
}

public static Result<T> Ok(T value) => new(value);
public static Result<T> Fail(string error) => new(error);

public Result<TOut> Bind<TOut>(Func<T, Result<TOut>> func)
{
return IsSuccess ? func(Value) : Result<TOut>.Fail(Error);
}
}


Теперь цепочка бизнес-логики выглядит так:
var result = Validate(input)
.Bind(SaveToDatabase)
.Bind(SendNotification);


Если валидация упала, SaveToDatabase и SendNotification не выполнятся. Ошибка пробросится по цепочке без try/catch и без вложенных if.

Когда вы видите повторяющийся паттерн «проверь, разверни, передай, заверни обратно», скорее всего перед вами монада. И вместо ручного кода для каждого случая можно написать один Bind и выстроить цепочку.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#il_люминатор
  • 🔥 19
  • ❤ 5
  • 👍 4
More from @csharpproglib
  1. Sep 21, 2026⚙️ Настоящие атомарные операции Если Volatile решает проблему видимости, то Interlocked ре…
  2. Sep 20, 2026💪 Разминка перед трудовыми буднями Что произойдёт? ❤️ — список станет [1, 3] 🔥 — Invalid…
  3. Sep 19, 2026🧩 Middleware в ASP.NET Core: 3 ловушки Middleware — звено HTTP pipeline: app.Use(async (c…
  4. Sep 18, 2026📍 Навигация: Вакансии • Задачи • Собесы 🐸Библиотека шарписта #garbage_collector
  5. Sep 17, 2026🦆 Duck typing в C# — почти как в TypeScript В TypeScript достаточно, чтобы у объекта был…
  6. Sep 17, 2026🧠 Почему .NET-под съедает всю память? kubectl top pod показывает 1 ГБ, а managed heap зан…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →