Библиотека шарписта | C#, F#, .NET, ASP.NET
22.4K subscribers
2.5K photos
40 videos
85 files
4.74K links
Все самое полезное для C#-разработчика в одном канале.

По рекламе: @proglib_adv

Учиться у нас: https://proglib.io/w/b60af5a4

Для обратной связи: @proglibrary_feeedback_bot

РКН: https://gosuslugi.ru/snet/67a5c81cdc130259d5b7fead
Download Telegram
⚙️ Под капотом async/await

Когда вы пишете асинхронный метод вроде:
async Task<int> GetDataAsync()
{
var data = await FetchAsync();
return data.Length;
}


вы можете думать, что это «всего лишь ожидание задачи». Но на самом деле компилятор C# превращает этот метод в state machine — конечный автомат, который управляет переходами между состояниями выполнения.

State machine — это структура, которая:

• хранит текущее состояние выполнения программы, например, до await, после await или завершено,

• знает, в какое состояние перейти дальше, когда наступает внешнее событие например, завершение Task.

Проще говоря, компилятор разворачивает ваш линейный асинхронный код в набор состояний + переключатель между ними.

Что делает компилятор

При компиляции метода с await создаётся вспомогательный класс, реализующий интерфейс IAsyncStateMachine.

Если упростить, то наш пример превращается примерно в:
private struct GetDataAsyncStateMachine : IAsyncStateMachine
{
public int _state;
public AsyncTaskMethodBuilder<int> _builder;
private TaskAwaiter<string> _awaiter;

public void MoveNext()
{
try
{
if (_state == 0)
{
// после await
var result = _awaiter.GetResult();
_builder.SetResult(result.Length);
return;
}

var task = FetchAsync();
if (!task.IsCompleted)
{
_state = 0;
_awaiter = task.GetAwaiter();
_builder.AwaitUnsafeOnCompleted(ref _awaiter, ref this);
return;
}

_builder.SetResult(task.Result.Length);
}
catch (Exception ex)
{
_builder.SetException(ex);
}
}

public void SetStateMachine(IAsyncStateMachine stateMachine) { }
}


Другие примеры использования state machines в C#

• yield return — генерация итераторов IEnumerator

• foreach на async коллекциях — асинхронные итераторы IAsyncEnumerable

Каждый await или yield превращает метод в "автомат", который сам управляет своим ходом выполнения.

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

#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
👍14🥱75
🚀 JetBrains .NET Days 2025

JetBrains снова собрала лучших инженеров и евангелистов .NET на своей онлайн-конференции .NET Days. Два дня плотных докладов — от чистой архитектуры и распределённых систем до GenAI и F#.

1️⃣ Самое интересное за первый день:

• Чистая архитектура с ASP.NET Core

Как выстроить проект так, чтобы код был читаемым, тестируемым и легко рефакторился. Разделение слоёв и зависимостей без боли.

• Nullability в C#: включаем защиту от NullReferenceException

Аннотации и статический анализ помогают избавиться от NullReferenceException и внедрить null safety даже в старый код.

• Как выбрать систему обмена сообщениями

Сравнение AWS SQS, RabbitMQ и Azure Service Bus — плюсы, минусы и типичные ошибки при выборе очередей сообщений.

• TDD на фронтенде с Blazor

Как применять TDD на фронтенде с Blazor и bUnit. Быстрая обратная связь и уверенность в каждом изменении.

2️⃣ Темы за второй день:

• Версионирование событийных систем

Безопасные приёмы эволюции событий: версионирование, апкастинг и совместимость без поломок у потребителей.

• Генеративный ИИ и .NET Aspire в действии


Интеграция LLM, управление контекстом и масштабируемая оркестрация с помощью Semantic Kernel и Aspire.

• Функциональное программирование в F#: мода или польза


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

• Как нашли утечку 2 ГБ в день за 5 минут


История о том, как обнаружить гигабайтные утечки памяти с помощью dotMemory в продакшене за считанные минуты.

Два дня — и десятки инсайтов о будущем .NET: от облаков и AI до функционального подхода и устойчивой архитектуры. Записи доступны на YouTube.

➡️ Первый день
➡️ Второй день

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

#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
7🔥4👍3
🔍 Как работать с массивами в C# без тормозов и без unsafe-кода

В C# каждое обращение к элементу массива проверяется на выход за границы. Это безопасно, но медленно. Разработчики часто решают проблему через unsafe-код с указателями — быстро, но опасно: один неверный индекс, и приложение крашится или получает дыру в безопасности.

Есть третий путь — Span<T>

Span — это структура, которая хранит указатель на данные и их длину. Фишка в том, что это ref struct — она живёт только в стеке и не может попасть в кучу. Благодаря этому компилятор гарантирует: данные переживут span, а значит проверки границ можно убрать.

Например, в быстрой сортровке вместо передачи массива с индексами low/high передаёте span. Код короче, переполнение невозможно, а рекурсивные вызовы работают через срезы:
// Было: опасно, (low + high) может переполниться
void Quicksort(int[] array, int low, int high) {
int mid = (low + high) / 2; // 💥 overflow!
// ...
Quicksort(array, low, pivot - 1);
Quicksort(array, pivot + 1, high);
}

// Стало: безопасно и выразительно
void Quicksort(Span<int> span) {
if (span.Length <= 1) return;

int pivot = Partition(span);
Quicksort(span[..pivot]); // левая часть
Quicksort(span[(pivot + 1)..]); // правая часть
}


Раньше для передачи части массива в функцию приходилось либо копировать данные, либо таскать массив + offset + length. Span решает это элегантно: создаёте срез array[10..20], передаёте дальше — никакого копирования, полная безопасность типов.

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

#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1711😁1
👨‍💻 Три структуры данных, которые игнорируются зря

List<T> — это универсальный солдат C#. Но универсальный не значит оптимальный. Пока вы перебираете 10 000 элементов через .FirstOrDefault(), Dictionary находит нужное за одно обращение. И это только начало.

Queue — когда порядок имеет значение

Обрабатываете задачи по очереди? Вот вам готовое решение:
var tasks = new Queue<UserRequest>();

tasks.Enqueue(new UserRequest { Id = 1, Name = "Alice" });
tasks.Enqueue(new UserRequest { Id = 2, Name = "Bob" });

// Безопасное извлечение (с .NET 6+)
while (tasks.TryDequeue(out var request))
{
await ProcessAsync(request);
}


Первым пришел — первым обработался. Никаких индексов, никаких сортировок.

Stack — для истории действий

Делаете undo/redo или навигацию? Стек решает это из коробки:
var history = new Stack<string>();

history.Push("/home");
history.Push("/products");
history.Push("/cart");

// Вернуться назад
if (history.TryPop(out var lastPage))
{
Console.WriteLine($"Back to: {lastPage}"); // /cart
}


Последнее действие всегда доступно первым. Логика браузерной кнопки «Hазад» готова.

Dictionary — когда нужна скорость

Поиск по ключу за O(1) вместо перебора всего списка:
var users = new Dictionary<int, User>();

users.Add(1, new User { Name = "Alice" });
users.Add(2, new User { Name = "Bob" });

// Безопасная проверка
if (users.TryGetValue(2, out var user))
{
Console.WriteLine(user.Name); // Bob
}


Никаких .FirstOrDefault(x => x.Id == 2) с перебором тысяч записей.

List отлично работает, когда вам нужен доступ по индексу и порядок элементов. Но если задача укладывается в одну из этих трёх структур — используйте их.

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

#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
🥱2410
🤨 Когда помощник становится врагом

Представьте, что вы отправили друга в магазин с деньгами на общий счёт. Он может потратить их на что угодно, при этом никому не отчитываясь. Вот примерно так же работает static с изменяемым состоянием. Удобно? Да. Безопасно? Совсем нет.

Почему static плохо сочетается с данными

Static — это ключевое слово, которое любят новички и используют для удобства. А потом из этого вырастают проблемы, которые отъедают часы отладки. Главная беда в том, что статические поля существуют всё время работы программы и доступны отовсюду.

Четыре золотых правила

1. Статические поля могут быть только для чтения. Лучше — константы.

2. Статические методы должны работать как математические функции: дали параметры — получили результат. Никаких побочных эффектов, никаких изменений состояния. Например: Select, Where, First из LINQ — это всё статические методы расширения, которые не трогают исходную последовательность.

3. Если в классе есть методы, которые не используют поля экземпляра, эту логику лучше вынести в отдельный статический класс-помощник. Это делает код чище, тесты проще.

4. Если вы создаёте статическое поле, пусть оно указывает на что-то, что не меняется. На объект конфигурации? Хорошо. На список данных, который вы потом модифицируете? Катастрофа.

Когда static — ваш друг

• Вспомогательные классы с чистыми функциями

Создайте статический класс StringUtils с методом static string ToPascalCase(string input). Нет состояния, нет побочных эффектов, нет проблем. Это хорошее использование.

• Методы расширения для преобразований

Select, Where и компания — статические методы расширения, которые берут данные, преобразуют их и возвращают новые. Функциональный подход, никакого волшебства.

• ThreadStatic для действительно отдельного состояния

Если каждому потоку нужно своё состояние, можно использовать ThreadStatic. Но даже здесь нужна осторожность — каждый поток всё равно должен управлять своим состоянием.

Передайте зависимость через конструктор, используйте DI-контейнер, создайте объект на сессию. Ваш будущий я, сидящий в отладчике в три утра, скажет вам спасибо.

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

#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
Расширяем типы по-новому

В C# 14 расширяется концепция расширений: можно создавать не только расширенные методы для экземпляров типов, но и свойства расширения, а также расширения, которые относятся к самому типу как к статическому элементу.

Пример:
public static class EnumerableExtensions
{
extension<TSource>(IEnumerable<TSource> source)
{
public bool IsEmpty => !source.Any(); // Расширенное свойство
}

extension<TSource>(IEnumerable<TSource>)
{
public static IEnumerable<TSource> Combine(IEnumerable<TSource> first, IEnumerable<TSource> second)
=> first.Concat(second); // Статический метод расширения
}
}


Конструкция extension<TSource>(IEnumerable<TSource> source) — это новый синтаксис блока расширения для экземпляра типа: здесь определяются расширяющие члены, которые «прикрепляются» к объекту IEnumerable<TSource>.
Внутри блока объявлено свойство IsEmpty, которое возвращает true, если последовательность пуста (!source.Any()).

Второй блок extension<TSource>(IEnumerable<TSource>) — расширение для самого типа IEnumerable<TSource> как статического члена.
Внутри него определён статический метод Combine, который принимает две последовательности и объединяет их с помощью Concat.

Как это использовать:
var list = new List<int> { 1, 2, 3 };

bool empty = list.IsEmpty; // false — вызов расширенного свойства как у экземпляра

var combined = Enumerable<int>.Combine(new[] { 1 }, new[] { 2, 3 }); // {1, 2, 3}


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

#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
👍15🔥53😁3
⚡️ Ключевое слово field упрощает свойства

Управление данными в свойствах теперь проще с новым ключевым словом для доступа к автоматически созданному полю.

Раньше для добавления логики установки значения использовалось отдельное поле и аксессоры:
private string _msg;
public string Message
{
get => _msg;
set => _msg = value ?? throw new ArgumentNullException(nameof(value));
}


Сейчас field ссылается на поле, которое создает компилятор, сокращая код:
public string Message
{
get;
set => field = value ?? throw new ArgumentNullException(nameof(value));
}


Минимум кода — максимум контроля: write-only свойства снаружи и строгая логика внутри.

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

#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
👍212😁2
🛠 Натуральные преобразования для Span и ReadOnlySpan

C# 14 позволяет неявно преобразовывать между Span<T>, ReadOnlySpan<T> и массивами, упрощая манипуляции с данными и повышая скорость:
ReadOnlySpan<char> roSpan = "hello";
Span<char> span = roSpan;
char[] array = roSpan.ToArray();


Типы Span теперь могут выступать как принимающие методы расширения, то есть станут получателями вызова таких методов. Они также могут комбинироваться с другими типами преобразований.

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

#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥821
⚡️ ConfigureAwait(false) — зачем это нужно

Вы пишете библиотеку на C#, используете async/await, и всё работает. Но потом ваша библиотека попадает в веб-приложение, мобильное приложение, или какой-то другой контекст — и начинаются странные deadlock'и или даже зависания.

Проблема вот в чём: когда код выходит из await, он хочет продолжить выполнение в том же контексте, в котором был запущен. Если это UI-приложение, контекст одноточечный — только главный поток может работать с UI.

Если ваша библиотека захватит этот контекст и не отпустит — приложение зависнет. А если написали синхронный код, который вызывает async библиотеку, и она захватит контекст — будет deadlock.

ConfigureAwait(false) как раз говорит: «Мне вообще всё равно, в каком контексте продолжать. Возьми любой поток из thread pool'а«. И вот это спасает приложения от проблем:
await stream.WriteAsync(buffer, ct).ConfigureAwait(false);


Результат: библиотека не блокирует контекст, не создаёт deadlock'и, работает везде одинаково.

Когда НЕ нужен ConfigureAwait(false):

В приложениях можно писать без него, если вам нужен контекст:
// В ASP.NET контроллере — можно без ConfigureAwait(false)
[HttpGet]
public async Task<IActionResult> GetData()
{
var data = await service.GetDataAsync(); // Контекст нужен для логирования, etc
return Ok(data);
}

// В UI приложении — нужен контекст
private async void Button_Click(object sender, EventArgs e)
{
var data = await LoadDataAsync(); // Нужно вернуться в UI поток
UpdateUI(data);
}


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

#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
12👍11🥱1
🧑‍💻 Контролируем параллельную работу

Представьте ситуацию: у вас есть 1000 элементов для обработки, и вы хотите запустить это параллельно. Но если просто запустить все одновременно, приложение упадёт — будет слишком много одновременных подключений к БД, API или файловой системе.

Здесь помогает SemaphoreSlim.

Как это работает

Создаём семафор с лимитом 4. Это значит, что максимум 4 потока могут находиться внутри защищённого блока одновременно.
await gate.WaitAsync(ct);


Поток ждёт в очереди, пока не появится "место". Если уже 4 потока внутри — новичок подождёт:
await gate.WaitAsync(ct);


После обработки обязательно вызываем Release() — это освобождает место для следующего потока. finally гарантирует, что это произойдёт даже если операция упадёт:
try { await Process(x, ct); }
finally { gate.Release(); }


Вместо 100 одновременных загрузок будет ровно столько, сколько вы укажете.

🔹 Алгоритмы и структуры данных
🔹 Получить консультацию менеджера
🔹 Сайт Академии 🔹 Сайт Proglib

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

#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11
📎 Частичные события и конструкторы

Новый уровень модульности в больших проектах: делим объявление и реализацию. Большие проекты требуют таких гибких способов организации кода.

В C# теперь можно объявлять события и конструкторы с модификатором partial. Это значит, что объявление может находиться в одном месте, а его реализация — в другом, обычно в другом файле:
public partial class MyClass
{
partial void OnInitialized();

public MyClass()
{
OnInitialized();
}
}


Здесь объявлена частичная функция OnInitialized() без тела — её реализация может появиться отдельно. Также конструктор вызывает эту функцию, если она реализована.

Частичные конструкторы поддерживают совместимость с платформами, использующими генерацию кода.

🔹 Практический интенсив «Архитектуры и шаблоны проектирования»
🔹 Получить консультацию менеджера
🔹 Сайт Академии 🔹 Сайт Proglib

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

#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔52🥱1
👨‍💻 Когда нужна стабильность, а не спешка

Представьте фоновую задачу, которая должна срабатывать каждые 5 секунд: проверка очереди, синхронизация данных, отправка метрик. Можно, конечно, использовать Task.Delay, но это хрупко — если одна итерация длится дольше, чем ожидалось, ритм собьётся.

PeriodicTimer решает эту проблему иначе:
var timer = new PeriodicTimer(TimeSpan.FromSeconds(5));


Создаём таймер, который будет срабатывать каждые 5 секунд. Точка отсчёта — момент создания.

while (await timer.WaitForNextTickAsync(ct))
{
await DoWork(ct);
}


WaitForNextTickAsync ждёт следующего тика таймера. Когда он приходит — выполняем работу. Если работа заняла 1 секунду, следующий тик всё равно придёт через 5 секунд от начала цикла, а не через 6.

Это стабильный интервал, а не интервал между завершением одной работы и началом другой.

🔹 Практический интенсив «Архитектуры и шаблоны проектирования»
🔹 Получить консультацию менеджера
🔹 Сайт Академии 🔹 Сайт Proglib

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

#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥20👍8
🛠 Как создать путь к временному файлу в C#

В C# для работы с путями к файлам существует класс Path, который предлагает методы для объединения частей путей и получения системных директорий. Один из таких методов — GetTempPath(). Он возвращает путь к папке, предназначенной для временных файлов на текущей системе.

Другой полезный метод — Combine(). Он соединяет несколько частей пути, учитывая особенности слэшей в Windows, Linux и macOS. Вместо ручного склеивания строк и риска ошибок, Combine() гарантирует правильный формат.

Пример:
using System;
using System.IO;

class Program
{
static void Main()
{
// Получаем путь к системной временной папке и добавляем имя файла
var path = Path.Combine(Path.GetTempPath(), "report.csv");
Console.WriteLine("Путь к временному файлу: " + path);

// Здесь можно сохранить файл, открыть или выполнить другие операции
}
}


Использование Path.GetTempPath() и Path.Combine() — простой способ получить корректный путь к временному файлу без ошибок и с учётом особенностей операционных систем.

🔸 Основы IT для непрограммистов
🔸 Получить консультацию менеджера
🔸 Сайт Академии 🔸 Сайт Proglib

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

#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
👍23🥱7
✏️ Null-условное присваивание

В обыденной работе с объектами часто приходится проверять, не равен ли объект null, прежде чем присваивать значение его свойствам. Это приводит к громоздким конструкциям с множеством if. Теперь с null-условным присваиванием можно написать проще и понятнее.

Операторы условного доступа ?. и ?[] теперь поддерживают использование слева от оператора присваивания. Раньше для безопасного присваивания требовалось писать так:
if (customer is not null)
{
customer.Order = GetCurrentOrder();
}


С null-условным присваиванием можно упростить до одной строки:
customer?.Order = GetCurrentOrder();


Особенность в том, что правая часть вычисляется только если левая часть не равна null. Если customer равен null, метод GetCurrentOrder() вызван не будет. Это оптимизирует производительность и упрощает логику.

Также это работает с составными операторами присваивания: +=, -=, *=, но не поддерживается для инкремента и декремента.
customer?.Total += 100;
// Но нельзя так:
// customer?.Total++;


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

#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
👍13🥱5
🧑‍💻 Проекция колонок вместо сущностей в запросах

Когда работаете с базой данных, лучше получить только нужные поля, а не всю сущность. Это ускорит запросы и снизит объём передаваемых данных.

Пример проекции колонок с использованием LINQ:
var dto = await db.Users
.Where(u => u.Id == id)
.Select(u => new UserDto(u.Id, u.Name))
.SingleOrDefaultAsync(ct);


Здесь из таблицы пользователей выбираются только Id и Name, которые помещаются в объект UserDto.

🔹 ML для старта в Data Science
🔹 Получить консультацию менеджера
🔹 Сайт Академии 🔹 Сайт Proglib

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

#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
🥰2
🔑 Управление HttpClient по ключам

С .NET 9 можно управлять зависимостями по ключам. Буквально регистрировать и получать, к примеру, конфиги для HttpClient.

Как это работает

AddAsKeyed() — новый метод расширения для регистрации именованных HttpClient как Keyed Service в DI. Имя клиента становится ключом, по которому можно получать конкретный экземпляр.

• Внедрение осуществляется с помощью атрибута [FromKeyedServices("key")] или через Func<string, HttpClient> для динамического выбора клиента по ключу.

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

Пример регистрации и использования:
var builder = WebApplication.CreateBuilder(args);

// Регистрация HttpClient с ключом "github"
builder.Services.AddHttpClient("github", c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
c.DefaultRequestHeaders.Add("User-Agent", "dotnet");
})
.AddAsKeyed();

var app = builder.Build();

// Внедрение HttpClient по ключу "github"
app.MapGet("/", ([FromKeyedServices("github")] HttpClient httpClient) =>
{
return httpClient.GetFromJsonAsync<Repo>("/repos/dotnet/runtime");
});

app.Run();

record Repo(string Name, string Url);


Преимущества Keyed DI

• Минимизирует необходимость вручную создавать и хранить экземпляры через IHttpClientFactory.

• Позволяет внедрять в сервисы и контроллеры правильно настроенные клиенты с простым указанием ключа.

• Улучшает читаемость и поддержку кода, особенно если в проекте много разных конфигураций HttpClient.

• Можно глобально включить ключевой DI для всех клиентов через ConfigureHttpClientDefaults.

Важные моменты

• По умолчанию AddAsKeyed() регистрирует HttpClient с областью видимости Scoped, но можно настроить и другой жизненный цикл.

• Нужно внимательно следить за временем жизни сервисов, чтобы избежать утечек памяти и проблем с соединениями.

• При ошибочном использовании ключа сервис выдаст стандартное исключение, помогая быстро находить и исправлять конфигурационные ошибки.

🔹 ML для старта в Data Science
🔹 Получить консультацию менеджера
🔹 Сайт Академии 🔹 Сайт Proglib

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

#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
9🔥9👍3