Библиотека шарписта | C#, F#, .NET, ASP.NET
Все самое полезное для C#-разработчика в одном канале. Наши курсы: https://clc.to/y3LDtw По рекламе: @proglib_adv Для обратной связи: @proglibrary_feeedback_bot РКН: https://gosuslugi.ru/snet/67a5c81cdc130259d5b7fead
Show more📈 Analytical overview of Telegram channel Библиотека шарписта | C#, F#, .NET, ASP.NET
Channel Библиотека шарписта | C#, F#, .NET, ASP.NET (@csharpproglib) in the Russian language segment is an active participant. Currently, the community unites 21 701 subscribers, ranking 5 955 in the Technologies & Applications category and 30 269 in the Russia region.
📊 Audience metrics and dynamics
Since its creation on невідомо, the project has demonstrated rapid growth, gathering an audience of 21 701 subscribers.
According to the latest data from 26 August, 2026, the channel demonstrates stable activity. Although there has been a change in the number of participants by -45 over the last 30 days and by 5 over the last 24 hours, overall reach remains high.
- Verification status: Not verified
- Engagement rate (ER): The average audience engagement rate is 15.84%. Within the first 24 hours after publication, content typically collects 7.84% reactions from the total number of subscribers.
- Post reach: On average, each post receives 3 438 views. Within the first day, a publication typically gains 1 702 views.
- Reactions and interaction: The audience actively supports content: the average number of reactions per post is 25.
- Thematic interests: Content is focused on key topics such as .net, шарписта, навигация, await, string.
📝 Description and content policy
The author describes the resource as a platform for expressing subjective opinions:
“Все самое полезное для C#-разработчика в одном канале.
Наши курсы: https://clc.to/y3LDtw
По рекламе: @proglib_adv
Для обратной связи: @proglibrary_feeedback_bot
РКН: https://gosuslugi.ru/snet/67a5c81cdc130259d5b7fead”
Thanks to the high frequency of updates (latest data received on 27 August, 2026), the channel maintains relevance and a high level of publication reach. Analytics show that the audience actively interacts with content, making it an important point of influence in the Technologies & Applications category.
AsParallel()
Когда задачи выполняются параллельно и часть из них падает с ошибкой, .NET не бросает одно исключение. Вместо этого все ошибки собираются в AggregateException. Если не обработать его правильно, приложение просто упадёт, а вы потеряете детали о каждой конкретной ошибке.
AggregateException содержит коллекцию InnerExceptions, где хранится каждое исключение из параллельного потока. Перебирая их в catch, мы получаем полный список того, что пошло не так, и можем реагировать на каждый случай отдельно.
Вот как это выглядит:
try
{
var results = items
.AsParallel()
.Select(item =>
{
if (item == null)
throw new InvalidOperationException("Item is null");
return ProcessItem(item);
})
.ToList();
}
catch (AggregateException ae)
{
foreach (var ex in ae.InnerExceptions)
{
Console.WriteLine($"Handled: {ex.Message}");
}
}
Если в items окажется null, код не упадёт молча. Каждая ошибка будет поймана и выведена отдельно.
Используйте этот подход всегда, когда логика внутри AsParallel() может выбросить исключение. Это особенно актуально при работе с внешними данными, файлами или сетевыми вызовами, где ошибки непредсказуемы.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#sharp_view- Разберем реальные кейсы стартапов и ограничения LLM. - Обсудим рабочие архитектуры: RAG, human-in-the-loop, контроль качества. - Ответим на ваши вопросы и разберем кейсы участников.🎁 Бонусы: в конце вебинара подарим промокод на скидку 10.000 ₽ на курсы и разыграем подписки на полезные AI-сервисы. 👉 Зарегистрироваться на вебинар
async void в C# выглядят безобидно, но скрывают ловушку. Если внутри такого метода возникнет исключение, поймать его стандартными средствами не получится. Оно просто вылетит в поток синхронизации и, скорее всего, уронит приложение.
Почему вообще существует async void? Всё просто: обработчики событий по соглашению возвращают void. Без поддержки async void нельзя было бы использовать async/await внутри них. Это единственный законный случай.
Официальное правило звучит так: async void допустим только как обработчик события, и такой метод не должен бросать исключения.
Правильный обработчик выглядит примерно так:
private async void Button_Click(object sender, EventArgs e)
{
try
{
await DoSomethingAsync();
}
catch (Exception ex)
{
// обрабатываем ошибку здесь, не даём ей вылететь наружу
MessageBox.Show(ex.Message);
}
}
Всё остальное — async Task или async Task<T>. Это позволяет нормально ловить исключения, ждать завершения и тестировать метод.
Если видите async void не в обработчике, это сигнал для ревью.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#sharp_viewvar user = (Name: "John", Age: 25);
Console.WriteLine(user.Name); // John
Console.WriteLine(user.Age); // 25
Никакого класса, никакой модели. Просто структура с именами прямо там, где она нужна.
Где это помогает
Чаще всего кортежи с именами используют в возвращаемых значениях методов. Вместо того чтобы создавать out-параметры или отдельный DTO, можно сделать так:
public (string Name, bool IsValid) Validate(string input)
{
var isValid = !string.IsNullOrWhiteSpace(input);
return (input.Trim(), isValid);
}
var result = Validate(" John ");
Console.WriteLine(result.Name); // John
Console.WriteLine(result.IsValid); // True
Читаемость не хуже, чем у класса, а кода меньше.
Деконструкция
Кортеж можно сразу разложить по переменным:
var (name, isValid) = Validate(" John ");
Console.WriteLine(name); // John
Console.WriteLine(isValid); // True
Это удобно, если дальше в коде поля используются отдельно, а сам кортеж не нужен.
Когда кортеж не подходит
Кортежи хороши для локального использования и возвращаемых значений. Но если структура данных:
- передаётся между слоями приложения
- сериализуется в JSON или хранится в базе
- используется более чем в двух-трёх местах
Тогда лучше создать полноценный класс или record. Кортеж не несёт никакой доменной семантики — это просто набор значений.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#sharp_viewExecutor<TInput, TOutput>, соединить их в граф через WorkflowBuilder и выбрать способ запуска, не меняя определение воркфлоу.
Поддерживаются паттерны:
- последовательные цепочки
- параллельный fan-out / fan-in
- условный роутинг (AddSwitch)
- человек в петле (human-in-the-loop через RequestPort)
- вложенные воркфлоу как sub-orchestrations
Как это работает
Описываем шаги:
internal sealed class OrderLookup()
: Executor<OrderCancelRequest, Order>("OrderLookup")
{
public override async ValueTask<Order> HandleAsync(
OrderCancelRequest message,
IWorkflowContext context,
CancellationToken cancellationToken = default)
{
await Task.Delay(100, cancellationToken);
return new Order(Id: message.OrderId, ...);
}
}
Собираем граф:
Workflow cancelOrder = new WorkflowBuilder(orderLookup)
.WithName("CancelOrder")
.AddEdge(orderLookup, orderCancel)
.AddEdge(orderCancel, sendEmail)
.Build();
Запускаем в памяти (для локальной разработки):
await using StreamingRun run =
await InProcessExecution.RunStreamingAsync(cancelOrder, input: cancelRequest);
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
{
if (evt is ExecutorCompletedEvent completed)
Console.WriteLine($"{completed.ExecutorId}: {completed.Data}");
}
Добавляем устойчивость
Для production подключаем Microsoft.Agents.AI.DurableTask. Определение воркфлоу остаётся прежним, меняется только хостинг:
dotnet add package Microsoft.Agents.AI.DurableTask --prerelease
Запускаем локальный эмулятор DTS:
docker run -d --name dts-emulator \
-p 8080:8080 -p 8082:8082 \
mcr.microsoft.com/dts/dts-emulator:latest
Дашборд доступен на http://localhost:8082. Там видно хронологию шагов, входные и выходные данные каждого executor'а.
Регистрируем воркфлоу с durable runtime:
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.ConfigureDurableWorkflows(
workflowOptions => workflowOptions.AddWorkflow(cancelOrder),
workerBuilder: b => b.UseDurableTaskScheduler(dtsConnectionString),
clientBuilder: b => b.UseDurableTaskScheduler(dtsConnectionString));
})
.Build();
Параллельные AI-агенты
Агенты подключаются напрямую через AsAIAgent и запускаются параллельно. Если процесс упал в середине, завершённые агенты не перезапускаются:
AIAgent physicist = chatClient.AsAIAgent(
"You are a physics expert. Be concise.", "Physicist");
AIAgent chemist = chatClient.AsAIAgent(
"You are a chemistry expert. Be concise.", "Chemist");
Workflow workflow = new WorkflowBuilder(parseQuestion)
.AddFanOutEdge(parseQuestion, [physicist, chemist])
.AddFanInBarrierEdge([physicist, chemist], aggregator)
.Build();
Одно определение воркфлоу работает везде: в памяти для разработки, с Durable Task для отказоустойчивости, на Azure Functions для serverless. Переключение между режимами не затрагивает код executor'ов.
➡️ Блог разработчиков
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#il_люминаторAsOrdered — когда порядок элементов важен в PLINQ
AsParallel() обрабатывает данные параллельно и не гарантирует, что результат выйдет в том же порядке, что и входная последовательность. Если порядок важен, нужен AsOrdered().
Что делает AsOrdered
Метод говорит PLINQ сохранять исходный порядок элементов в результирующей коллекции. Параллельная обработка при этом остаётся, но результат будет упорядочен так же, как входные данные.
Зачем это нужно
Есть случаи, когда порядок критичен — например, рендеринг элементов в UI или формирование отчёта, где строки должны идти в определённой последовательности. Без AsOrdered() результат непредсказуем.
Пример использования:
var results = items
.AsParallel()
.AsOrdered()
.Select(ProcessItem)
.ToList();
PLINQ обработает элементы параллельно, но в results они окажутся в том же порядке, что и в items.
Что учитывать
AsOrdered() добавляет накладные расходы, ведь PLINQ вынужден отслеживать позиции элементов и собирать результат в нужном порядке. На больших объёмах данных это заметно. Поэтому используйте метод только там, где порядок действительно нужен, а не по умолчанию.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#sharp_viewDEFERRABLE INITIALLY DEFERRED решает эту проблему — ограничение откладывается до конца транзакции.
Что это и зачем
По умолчанию PostgreSQL проверяет ограничения (FOREIGN KEY, UNIQUE, CHECK) сразу при выполнении каждого оператора. Если порядок вставки нарушает ссылочную целостность — получаем ошибку, даже если к концу транзакции всё было бы корректно.
DEFERRABLE INITIALLY DEFERRED говорит базе: не проверяй ограничение после каждой операции — подожди до COMMIT.
Это удобно, когда:
- записи ссылаются друг на друга (циклические FK)
- порядок вставки нельзя гарантировать
- нужно пакетно импортировать данные без временного отключения ограничений
Пример схемы
Допустим, есть две таблицы, которые ссылаются друг на друга:
CREATE TABLE teams (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
lead_id INT REFERENCES employees(id) DEFERRABLE INITIALLY DEFERRED
);
CREATE TABLE employees (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
team_id INT REFERENCES teams(id) DEFERRABLE INITIALLY DEFERRED
);
Без DEFERRABLE INITIALLY DEFERRED вставить команду со ссылкой на сотрудника, которого ещё нет — невозможно. С ним — всё проверяется один раз при COMMIT.
Как использовать в C# с Npgsql
Npgsql поддерживает транзакции напрямую. Главное — убедиться, что обе вставки выполняются в одной транзакции.
await using var conn = new NpgsqlConnection(connectionString);
await conn.OpenAsync();
await using var tx = await conn.BeginTransactionAsync();
// Вставляем команду, но сотрудник ещё не существует
await using (var cmd = new NpgsqlCommand(
"INSERT INTO teams (id, name, lead_id) VALUES (1, 'Backend', 42)", conn, tx))
{
await cmd.ExecuteNonQueryAsync();
}
// Теперь вставляем сотрудника
await using (var cmd = new NpgsqlCommand(
"INSERT INTO employees (id, name, team_id) VALUES (42, 'Иван', 1)", conn, tx))
{
await cmd.ExecuteNonQueryAsync();
}
// FK проверяются здесь — оба существуют, всё ок
await tx.CommitAsync();
Без DEFERRABLE INITIALLY DEFERRED первая вставка упала бы с ошибкой foreign key violation, потому что employees(42) не существует в момент вставки команды.
Как использовать с EF Core
EF Core сам по себе не умеет объявлять DEFERRABLE ограничения через Fluent API. Но можно сделать через HasCheckConstraint с raw SQL или через миграцию.
Самый надёжный способ — написать миграцию вручную:
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(@"
ALTER TABLE teams
DROP CONSTRAINT IF EXISTS teams_lead_id_fkey;
ALTER TABLE teams
ADD CONSTRAINT teams_lead_id_fkey
FOREIGN KEY (lead_id) REFERENCES employees(id)
DEFERRABLE INITIALLY DEFERRED;
");
}
После этого транзакции EF Core будут пользоваться отложенной проверкой автоматически — ничего дополнительно настраивать не нужно.
Разница между DEFERRED и IMMEDIATE
DEFERRABLE означает, что ограничение можно отложить. INITIALLY DEFERRED — что оно отложено по умолчанию для каждой транзакции.
Если нужно временно изменить поведение внутри конкретной транзакции — можно явно переключить режим:
-- Внутри транзакции включить немедленную проверку
SET CONSTRAINTS teams_lead_id_fkey IMMEDIATE;
-- Или отложить конкретное ограничение
SET CONSTRAINTS teams_lead_id_fkey DEFERRED;
В C# через Npgsql это выполняется как обычный SQL-запрос внутри транзакции:
await using var cmd = new NpgsqlCommand(
"SET CONSTRAINTS ALL DEFERRED", conn, tx);
await cmd.ExecuteNonQueryAsync();
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#il_люминаторCancellationToken как параметр. Это правильная практика: метод должен уметь реагировать на отмену. Но что делать, если в конкретном месте отмена просто не нужна?
Что такое CancellationToken.None
CancellationToken.None — это пустой токен. Его свойство CanBeCanceled всегда возвращает false. Отменить его невозможно, и никакие CancellationTokenSource к нему не привязаны.
Используется там, где токен нужно передать по сигнатуре, но реальной логики отмены нет:
await DoWorkAsync(CancellationToken.None);
Альтернатива через default
В C# есть эквивалентный способ:
await DoWorkAsync(default);
// или явно
await DoWorkAsync(default(CancellationToken));
default(CancellationToken) и CancellationToken.None равны между собой. Два пустых токена всегда равны, вне зависимости от того, как они созданы.
Когда применять
Типичный сценарий: вызов метода в Main, в тестах или в коде инициализации, где нет смысла настраивать отмену, но метод её принимает.
// Тест без отмены
var result = await repository.GetAllAsync(CancellationToken.None);
// Инициализация при старте приложения
await migrationService.RunAsync(CancellationToken.None);
Что не стоит делать
Передавать new CancellationToken() вместо CancellationToken.None. Технически результат тот же, но это создаёт ложное ощущение, что токен где-то настроен. CancellationToken.None читается однозначно: отмены здесь нет намеренно.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#sharp_viewWithDegreeOfParallelism: как управлять параллелизмом в PLINQ
По умолчанию PLINQ сам решает, сколько потоков использовать, опираясь на количество ядер процессора. Это удобно, но не всегда подходит.
Что делает WithDegreeOfParallelism
Метод позволяет вручную задать максимальное число потоков, которые PLINQ будет использовать при обработке данных. Это прямой контроль над тем, сколько ресурсов займёт параллельная операция.
Зачем это нужно
Если задачи тяжёлые по CPU, без ограничений PLINQ может «съесть» все ядра. Остальные части приложения начнут тормозить — особенно заметно в десктопных приложениях, где есть UI-поток.
Пример использования:
var results = items
.AsParallel()
.WithDegreeOfParallelism(4)
.Select(ProcessItem)
.ToList();
Здесь мы явно говорим PLINQ использовать не больше 4 потоков, независимо от того, сколько ядер на машине.
Когда применять
Метод полезен в двух ситуациях:
1. Обработка больших файлов или тяжёлых вычислений, когда нужно оставить ресурсы для UI или других процессов.
2. Когда пул потоков по умолчанию перегружен и это влияет на другие операции в приложении.
Устанавливайте значение исходя из реальной нагрузки. Четыре потока это не универсальный ответ, это отправная точка для тестирования.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#il_люминаторwhile (!task.IsCompleted)
{
Thread.Sleep(100);
}
Выглядит логично, но на практике это сводит на нет весь смысл асинхронности.
Что происходит под капотом
Поток не освобождается. Он занят проверкой статуса в цикле и не делает ничего полезного. Процессор тратит такты на холостые итерации, хотя эти ресурсы могли бы использоваться другим кодом, в том числе самой ожидаемой операцией.
Это особенно критично в серверных приложениях, где количество потоков ограничено. Заблокированный поток в пуле означает, что следующий запрос будет ждать.
Используйте await:
await task;Это освобождает поток до завершения задачи. Планировщик вернёт управление в нужный момент без лишних проверок и потраченных ресурсов. 📍 Навигация: Вакансии • Задачи • Собесы 🐸 Библиотека шарписта #sharp_view
IChatClient из Microsoft Extensions for AI (MEAI) и поддерживает Azure OpenAI, OpenAI, GitHub Models, Foundry Local и Ollama.
Первый агент
Установка:
dotnet add package Microsoft.Agents.AI
Простейший агент:
AIAgent agent = new AzureOpenAIClient(
new Uri(endpoint),
new DefaultAzureCredential())
.GetChatClient(deploymentName)
.AsAIAgent(
instructions: "You are good at telling jokes.",
name: "Joker");
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate."));
Метод .AsAIAgent() это аналог .AsIChatClient(), только оборачивает клиент в агента с поддержкой сессий, инструментов и памяти. Стриминг тоже работает из коробки через RunStreamingAsync.
Инструменты
Агенты вызывают функции сами, без явных условий в коде. Главное добавить атрибуты [Description], чтобы модель понимала, когда и как использовать инструмент:
[Description("Get the weather for a given location.")]
static string GetWeather(
[Description("The location to get the weather for.")] string location)
=> $"The weather in {location} is cloudy with a high of 15°C.";
AIAgent agent = ...AsAIAgent(
instructions: "You are a helpful assistant",
tools: [AIFunctionFactory.Create(GetWeather)]);
Спрашиваем «What is the weather in Amsterdam?» и агент сам вызывает GetWeather("Amsterdam") и подставляет результат в ответ.
Многоходовые разговоры
Сессия сохраняет историю между вызовами:
AgentSession session = await agent.CreateSessionAsync();
await agent.RunAsync("Tell me a joke about a pirate.", session);
await agent.RunAsync("Now add emojis and tell it as a parrot.", session);
Сессию можно сериализовать и восстановить:
JsonElement state = await agent.SerializeSessionAsync(session);
var restored = await agent.DeserializeSessionAsync(state);
Память между сессиями
AIContextProvider позволяет агенту извлекать и помнить факты о пользователе между сессиями. Провайдер работает в два этапа:
StoreAIContextAsync запускается после каждого взаимодействия — здесь агент учится из разговора (например, извлекает имя пользователя).
ProvideAIContextAsync запускается до — здесь агент получает накопленный контекст перед ответом.
Провайдеры стекуются: один хранит имя, другой — предпочтения, третий — подтягивает документы из VectorData.
Граф-воркфлоу и мульти-агенты
Для сложных задач можно соединять агентов через граф: WorkflowBuilder связывает исполнителей рёбрами:
Писатель-критик — агент пишет, второй проверяет, если не ок — отправляет обратно:
WorkflowBuilder builder = new(writerAgent);
builder
.AddEdge(writerAgent, criticAgent)
.AddEdge(criticAgent, writerAgent, condition: result => !result.IsApproved)
.WithOutputFrom(criticAgent, condition: result => result.IsApproved);
Помимо этого доступны параллельное выполнение (fan-out / fan-in), условная маршрутизация, вложенные воркфлоу и циклы с лимитом итераций.
Human-in-the-loop
Для чувствительных операций: запись в БД, финансовые транзакции, отправка сообщений; агент может запрашивать подтверждение человека перед вызовом инструмента. Механизм встроен через FunctionApprovalRequestContent и FunctionApprovalResponseContent из MEAI.
Теперь Microsoft Agent Framework — это единый SDK с понятными абстракциями поверх уже знакомого IChatClient.
➡️ Блог разработчиков
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#sharp_viewextension:
extension(string text)
{
public int WordCount()
{
return text.Split(' ').Length;
}
}
Меньше шаблонного кода. Не нужно каждый раз создавать статический класс и писать this в параметре. Обязательно к использованию в новых проектах!
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#sharp_view