C# 1001 notes
6.53K subscribers
321 photos
9 videos
2 files
307 links
Регулярные короткие заметки по C# и .NET.

Просто о сложном для каждого.

admin - @haarrp
加入频道
👶 Junior developer пишет: notInactive, tempBool
🧑 Middle developer пишет: hadSubscriptionOnceUponATime
🧓 Senior developer пишет: hasSubscription, isActive

🤔 Почему так происходит?

👉 Потому что булевы переменные — просты по сути, но часто используются неинтуитивно, что делает код запутанным и плохо поддерживаемым.

Вот 7 простых правил, как работать с булевыми значениями в C# и резко улучшить читаемость вашего кода:

1. 🚫 Избегайте двойных отрицаний

user.IsNotActive
user.IsActive

user.HasNoDept
user.HasDept

creditCard.IsNotExpired
creditCard.IsExpired

Двойные отрицания сбивают с толку и делают логику трудной для чтения.

2. Используйте понятные префиксы

Is: user.IsActive
Has: user.HasDept
Should: order.ShouldBeCanceled

Префиксы мгновенно раскрывают назначение переменной.

3. 🔤 Предпочитайте единственное число

areUsers
hasUsers

Единственное число делает значение ясным и конкретным.

4. 🎯 Используйте прилагательные для описания состояния

CancelOrder
IsOrderCanceled

Булевы переменные должны описывать состояние, а не действие.

5. Используйте настоящее время

card.WasExpired
card.IsExpired

Текущие состояния — проще и универсальнее в коде.

6. 🔁 Принцип раннего возврата (Return Early)

Пишите условия и булевы проверки так, чтобы код читался сверху вниз, как история — коротко, логично, понятно.

7. ⚠️ Не передавайте булевы параметры

SendEmail(true)
SendEmail(SendMode.Immediate)

Используйте перечисления или именованные константы — они понятны без документации.

📌 Вывод:
Хорошие имена булевых переменных — это не косметика, а основа читаемости и архитектурной чистоты.
Хорошо названный isActive понятнее, чем любые комментарии и тесты.

💬 А ты какие правила используешь для наименования булевых переменных? Делись в комментариях 👇
[Best Practice] Явная конфигурация приложений ASP.NET Core

В большинстве существующих .NET-проектов конфигурация оказывается неочевидной, хотя в экосистеме .NET уже есть всё необходимое для ясной и понятной настройки ASP.NET Core веб-приложений с DI.

Как устроена конфигурация ASP.NET Core
- Источники (в порядке приоритета):
1. Host configuration
2. appsettings.json
3. appsettings.{Environment}.json
4. User secrets (в среде Development)
5. переменные окружения
6. аргументы командной строки
При дублировании ключей побеждает источник, указанный позже :contentReference[oaicite:0]{index=0}.

Выбор способа доступа к настройкам
- Через IConfiguration
- Через IOptions<T> / IOptionsMonitor<T>
Предпочтение паттерну Options даёт типобезопасный доступ к группам настроек и делает их использование очевидным :contentReference[oaicite:1]{index=1}.

Где хранить разные параметры
- `appsettings.json` — общие настройки (без секретов).
- `appsettings.{Environment}.json` — переопределения для конкретного окружения.
- User Secrets (`secrets.json`) — локальные секреты (пароли, ключи API); не используйте appsettings.json или appsettings.Development.json для них :contentReference[oaicite:2]{index=2}.
- Azure Key Vault — производственные секреты через builder.Configuration.AddAzureKeyVault(...) :contentReference[oaicite:3]{index=3}.
- Переменные окружения и CLI — для срочных или разовых переопределений.

Рекомендации по внедрению
- Избегайте дублирования ключей — задавайте значение только там, где оно действительно меняется.
- В README укажите, что разработчикам нужно получить secrets.json из менеджера паролей и настроить User Secrets.
- В IaC-скриптах чётко обозначьте необходимые секреты и параметры для каждого окружения.

Преимущества подхода
- Новым участникам проекта легко разобраться в конфигурации — каждый ключ встречается в одном месте.
- Возрастает поддерживаемость и прозрачность настроек.
- Упрощается автоматизация развёртывания (IaC): сразу видно, какие секреты и параметры требуются.

Читать
In-memory кэш в .NET: плюсы и минусы

Плюсы:
• Очень быстрый
• Легко реализуется
• Не требует внешних зависимостей

⚠️ Минусы:
• Данные теряются при перезапуске
• Работает только в пределах одного инстанса
• Нет шаринга между приложениями

📌 Нужно больше — переходите на распределённый кэш.

Redis — топовый выбор, и в .NET есть два стабильных клиента:
StackExchange.Redis
Microsoft.Extensions.Caching.StackExchangeRedis

📘 Хотите понять, когда и как использовать оба подхода?
Вот полный гайд:
https://milanjovanovic.tech/blog/caching-in-aspnetcore-improving-application-performance
🖥 Задача «Перехват исключений и потеря контекста» (C# 12)

Дан код:


using System;
using System.Runtime.ExceptionServices;

class Program
{
static void Main()
{
try
{
Console.WriteLine(Foo());
}
catch (Exception ex)
{
Console.WriteLine($"Catch: {ex.Message}");
}
}

static int Foo()
{
try
{
return Bar();
}
finally
{
throw new Exception("finally in Foo");
}
}

static int Bar()
{
try
{
throw new Exception("error in Bar");
}
finally
{
Console.WriteLine("Bar finally executed");
}
}
}


Вопросы:
• Что напечатает программа и почему?
• В каком порядке выполняются блоки try / finally?
• Какая из двух ошибок дойдёт до catch, а какая потеряется?
• Как изменить код, чтобы не терять информацию о первой ошибке?

Разбор:
• Bar бросает Exception("error in Bar"). Прежде чем исключение покинет метод, выполняется его finally, выводя Bar finally executed.
• Управление переходит в Foo. Там срабатывает finally, который бросает новое исключение ("finally in Foo").
• По правилам CLR новое исключение из finally заменяет текущее. Первая ошибка пропадает.
• В Main перехватывается только «finally in Foo».

Консольный вывод:

Bar finally executed
Catch: finally in Foo

Как сохранить обе ошибки

Обернуть второе исключение так, чтобы первая причина не потерялась, например:


finally
{
var pending = new Exception("finally in Foo");
throw new AggregateException("Foo failed", pending);
}


или переслать исходное исключение через ExceptionDispatchInfo:

Exception pending = null;

static int Foo()
{
try
{
return Bar();
}
catch (Exception ex)
{
pending = ex; // сохраняем первую причину
throw;
}
finally
{
if (pending != null)
ExceptionDispatchInfo.Capture(pending).Throw();
}
}```

Так ни одна ошибка не будет потеряна, а отладка станет нагляднее.
Please open Telegram to view this post
VIEW IN TELEGRAM
🖥Быстрая сортировка (QuickSort) с использованием рекурсии

Проблема: cортировка больших массивов может быть неэффективной при использовании простых алгоритмов, таких как сортировка пузырьком или вставками.

Решение: Автор в книге Algorithms and Data Structures for OOP With C# демонстрирует реализацию QuickSort — одного из самых эффективных алгоритмов сортировки на практике, с рекурсивным разбиением массива.

Пример кода:

public class QuickSortExample
{
public void QuickSort(int[] arr, int low, int high)
{
if (low < high)
{
int pi = Partition(arr, low, high);

QuickSort(arr, low, pi - 1);
QuickSort(arr, pi + 1, high);
}
}

private int Partition(int[] arr, int low, int high)
{
int pivot = arr[high];
int i = (low - 1);

for (int j = low; j < high; j++)
{
if (arr[j] < pivot)
{
i++;
(arr[i], arr[j]) = (arr[j], arr[i]);
}
}

(arr[i + 1], arr[high]) = (arr[high], arr[i + 1]);
return i + 1;
}
}


Преимущества:
— Быстрая сортировка даже больших наборов данных
— Средняя сложность O(n log n)
— Эффективное использование памяти за счет рекурсии
Please open Telegram to view this post
VIEW IN TELEGRAM
Трюк: добавление элементов в словарь со списками через `??=`


var dict = new Dictionary<string, List<int>>();
string key = "numbers";
int item = 42;

// Обычный способ:
if (!dict.TryGetValue(key, out var list))
{
list = new List<int>();
dict[key] = list;
}
list.Add(item);

// Короткий трюк:
(dict[key] ??= new List<int>()).Add(item);


Разбор:

- При обращении к dict[key] отсутствующий ключ вернёт default(List<int>), то есть null.

- Оператор ??= проверяет левую часть на null и, если она null, присваивает справа новое значение.

- В нашем случае, если dict[key] был null, создаётся новый List<int> и сразу сохраняется в словаре.

- После этого метод .Add(item) вызывается уже на существующем списке.

- В результате за одну строчку мы и проверили наличие, и создали новый список при необходимости, и добавили элемент.
🎯 Using .NET Aspire With the Docker Publisher — практическое руководство от Milan Jovanović

.NET Aspire — это современный фреймворк от Microsoft для создания облачных микросервисов. В статье показано, как автоматически интегрировать Docker Compose в .NET‑приложение с помощью нового инструмента — Aspire Docker Publisher.

Что вы узнаете:

1. Как описать окружение прямо в C#:

builder.AddDockerComposeEnvironment("aspire-docker-demo");

var postgres = builder.AddPostgres("database").WithDataVolume();
var redis = builder.AddRedis("cache");

var webApi = builder.AddProject<Projects.Web_Api>("web-api")
.WithReference(postgres).WaitFor(postgres)
.WithReference(redis).WaitFor(redis);

builder.Build().Run();


2. Как опубликовать проект:

dotnet tool install --global aspire.cli --prerelease
aspire publish -o docker-compose-artifacts

После чего автоматически создаётся docker-compose.yml и .env.

3. Что входит в результат:
- Готовый docker-compose.yml со всеми зависимостями
- Поддержка портов, переменных среды, volume и сетей
- Полная инфраструктура, которую можно деплоить хоть на VPS

4. Как это работает на проде:
- Всё, что нужно: скопировать артефакты → docker compose up -d
- Можно легко обернуть через Nginx или Traefik, подключить SSL

🧠 Почему это удобно:
- Не нужно вручную писать YAML — всё в коде
- Повышается воспроизводимость и читаемость инфраструктуры
- Упрощает переход от локальной разработки к боевому деплою

🔗 Статья: www.milanjovanovic.tech/blog/using-dotnet-aspire-with-the-docker-publisher
🧠 .NET-задача для продвинутых: потокобезопасная очередь с приоритетами и отменой задач

Задача:

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

1. Есть очередь задач (`Func<CancellationToken, Task>`), каждая с целочисленным приоритетом (0 — самый высокий).
2. Задачи выполняются параллельно, но не более N одновременно.
3. Если приходит задача с более высоким приоритетом, и нет свободных слотов, она может вытеснить задачу с самым низким приоритетом.
4. Вытеснённая задача должна быть отменена (через `CancellationToken`), и её ресурсы — корректно освобождены.
5. Код должен быть потокобезопасным и устойчивым к гонкам.

Дополнительно:

- Используйте PriorityQueue, SemaphoreSlim, CancellationTokenSource
- Не допускайте deadlock’ов
- Обеспечьте корректное завершение планировщика по команде StopAsync()

Пример API:

public class PriorityTaskScheduler
{
public PriorityTaskScheduler(int maxParallelism);

public Task EnqueueAsync(Func<CancellationToken, Task> task, int priority);

public Task StopAsync();
}


@csharp_1001_notes
⚠️ Как вы обрабатываете ошибки в C#?

Многие используют исключения для управления потоком и быстрого фейла. Но в C# метод не сообщает, какие именно исключения он может выбросить — это не видно в сигнатуре.

🔍 Мой подход:
Исключения — только для исключительных ситуаций.

Если метод может ожидаемо провалиться, пусть это будет явно.

Используйте Result-паттерн:

— Метод возвращает Result<T> вместо выбрасывания исключения
— Caller обязан проверить IsSuccess и обработать ошибку
— Код становится предсказуемее и легче тестируется
— Дополнительно: пропускная способность может быть выше, чем при throw/catch

Пример:

Result<User> result = userService.FindById(id);
if (!result.IsSuccess)
return Error(result.Error);


Подробнее
🖥 Топ-10 ошибок .NET‑разработчиков

Если ты пишешь на .NET — возможно, ты хотя бы раз совершал одну из этих архитектурных (или просто утомительных) ошибок. Вот список анти-паттернов.

1. Blazor вместо React
Переизобретать веб на C# ради UI? Ты не Google. Если нужен зрелый фронт, бери то, что уже доказало свою масштабируемость.

2. Serverless Azure Functions на одном App Service Plan
"О, это же серверлесс!" — пока не замечаешь, что всё сидит на одном сервисе. Масштабируемость мнимая, расходы настоящие.

3. Самодельный MVC поверх Minimal APIs
Зачем ты пишешь свою обвязку маршрутов, контроллеров и зависимостей, если MVC уже всё это умеет?

4. Хранимки для CRUD
Хранимки — не антипаттерн, но когда их 200 штук для банального Insert/Update/Delete — это просто ORM с человеческим лицом, только сложнее.

5. "Юнит-тесты", которым нужен деплой БД
Это не юнит-тесты. Это интеграционные, а если быть честным — "fragile-тесты".

6. AutoMapper
В теории красиво, в проде — «а почему здесь null?». Иногда проще написать руками и понять, что происходит.

7. Игнор Microsoft Orleans
Если делаешь real-time, pub/sub, воркеры — Orleans стоит хотя бы попробовать. Он закрывает много боли, которую ты иначе сам будешь собирать по кускам.

8. Начинать с "микросервисов"
Монолит — не зло. Зло — это 12 проектов в solution, которые общаются через HTTP и собираются 10 минут.

9. gRPC для фронта
Если у тебя браузер → JS → gRPC → base64 → JSON — просто остановись. Возьми REST или GraphQL и живи спокойно.

10. Razor Pages вместо MVC
Да, они проще на старте. Но когда проект растёт, ты захочешь нормальное разделение по слоям, маршрутам и структуре.

🧠 Вывод: не все возможности .NET надо использовать. Иногда сила — в простоте.

C чем согласны ? Что добавили бы ?


#dotnet #csharp #webdev #архитектура #программирование
Please open Telegram to view this post
VIEW IN TELEGRAM
🔐 Блог DevelopersVoice выпустил отличный гайд по **10 главным уязвимостям веб‑приложений с примерами на .NET.

Что внутри:
• Инъекции и XSS
• Ошибки аутентификации
• Уязвимые зависимости
• SSRF и плохая конфигурация
• Проблемы с логированием и безопасным дизайном

📌 Всё с практическими советами: как обнаружить, как исправить, как не допустить.

Полный гайд тут: https://developersvoice.com/blog/secure-coding/owasp-top-ten

#OWASP #SecureCoding #DotNet #WebSecurity #DevTips
📌 PolySharp — удобный способ использовать новые фичи C# на старых версиях .NET. Этот NuGet-пакет работает как source-генератор, автоматически подбирая нужные полифиллы в зависимости от целевой платформы. Для работы достаточно добавить ссылку на PolySharp, установить последнюю версию C# и можно писать современный код даже для .NET Framework или UWP.

Инструмент обладает умной генерацией только необходимых типов. Например, если компилятору C# 13 нужен [IsExternalInit] для init-only свойств, PolySharp создаст его за кулисами. При этом он не трогает фичи, требующие поддержки рантайма, но покрывает огромный пласт синтаксических улучшений — от nullable-аннотаций до интерполированных строковых обработчиков.

🤖 GitHub

@csharp_ci
Какое исключение выдается, если протокол, поддерживаемый префиксом URI, недействителен?
Anonymous Quiz
10%
URLNotFound
29%
NotSupportedException
48%
UriFormatException
12%
URLSourceNotFound
🔥 .NET Aspire: как упростить Service Discovery в микросервисах

Вместо ручной настройки адресов и портов, фреймворк .NET Aspire позволяет упростить обнаружение сервисов с помощью декларативного подхода.

🔹 Конфигурация через .AddProject() и .WithReference()
🔹 Сервисы автоматически "обнаруживаются" и подключаются
🔹 Поддержка локальной разработки, контейнеров и облака
🔹 Логи, метрики, health checks — встроены

📌 Aspire — это не просто упрощение разработки, это фундамент для масштабируемых .NET-приложений.

🧠 Полный разбор — в блоге:
[how-dotnet-aspire-simplifies-service-discovery](https://www.milanjovanovic.tech/blog/how-dotnet-aspire-simplifies-service-discovery)

Подпишись, чтобы не пропускать важные новинки .NET и облачной разработки.
😈 Хитрая задачка на C# — замыкания и ловушка в цикле

Что выведет этот код?


var actions = new List<Action>();

for (int i = 0; i < 5; i++)
{
actions.Add(() => Console.WriteLine(i));
}

foreach (var action in actions)
{
action();
}


На первый взгляд кажется, что будет:


0
1
2
3
4

Но на самом деле вывод:


5
5
5
5
5


💡 Почему?
Все лямбды замкнулись на одну и ту же переменную i, и когда они выполняются — i уже стало 5.

Как исправить:


for (int i = 0; i < 5; i++)
{
int copy = i;
actions.Add(() => Console.WriteLine(copy));
}


Теперь всё работает как ожидается.

🧠 Замыкания в C# захватывают переменные, а не их значения! Аккуратнее с циклами и лямбдами.
Почти каждый разработчик допускал эту ошибку.

Что не так с этим кодом?

На первый взгляд всё кажется логичным:

🔸API-эндпоинт регистрации пользователя вызывает UserService
🔸UserService сохраняет пользователя в базу и вызывает EmailService
🔸EmailService через SmtpClient отправляет письмо

Но если присмотреться, метод SendWelcomeEmail объявлен как async void.

В чём проблема с async void?

Вот суть:
async void делает невозможным отлов исключений.

Если внутри SendEmailAsync() произойдёт исключение — catch его не перехватит.
Вместо этого приложение может тихо упасть или начать вести себя непредсказуемо.

Почему так происходит?

Методы async void не возвращают Task, поэтому вызывающий код не может их await-ить и обрабатывать ошибки.
Исключения из async void проходят мимо стандартных механизмов обработки.

Правильный подход:

Всегда возвращай Task

Запомни: async void допустим только для обработчиков событий, где возвращаемый void обязателен.
Please open Telegram to view this post
VIEW IN TELEGRAM
Рефлексия тормозит приложения? Пора управлять компилятором!

Устали писать одинаковый код снова и снова? Рефлексия съедает производительность в рантайме, а генерация boilerplate-кода отнимает часы. Современный C# предлагает элегантное решение — заставить компилятор работать на вас.

🎥 Вебинар «Заставьте компилятор работать на вас: магия метапрограммирования с Source Generators в C#»
📅 6 августа, 20:00
👩‍🏫 Елена Сычева

Разберём на практике:
→ Почему рефлексия — узкое место и как Source Generators решают проблему на этапе компиляции
→ Устройство компилятора Roslyn: синтаксические деревья и семантическая модель
→ Live-coding первого Source Generator для избавления от рутинного кода
→ Применение генераторов в популярных библиотеках .NET 7+

Результат: вы научитесь создавать собственные генераторы кода, которые автоматизируют рутину и повышают производительность приложений без runtime-накладных расходов.

Вебинар проходит в рамках курса «C# углубленный» — участники получают эксклюзивные условия обучения.

→ Регистрация: https://otus.pw/X1GN/

Реклама. ООО "ОТУС ОНЛАЙН-ОБРАЗОВАНИЕ", ИНН: 9705100963
Правильный способ построения асинхронных API в .NET

Большинство API работают по простому шаблону:

🔸Клиент отправляет запрос
🔸Сервер выполняет работу
🔸Сервер возвращает ответ

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

А что насчёт длительных операций?

Речь о задачах вроде:

> обработки больших файлов
> генерации отчётов
> конвертации видео

Такие процессы могут занимать от нескольких минут до часов.

Вот как можно правильно строить асинхронные API: читать

Как сообщить клиенту, что его запрос обработан?

Есть два подхода:

🔸PULL — клиент опрашивает API, чтобы узнать статус

🔸PUSH — сервер сам уведомляет клиента (через WebSocket, email и т.д.)
Please open Telegram to view this post
VIEW IN TELEGRAM
Хотите разобраться в тестировании API на ASP.NET Core?

➡️ Присоединяйтесь к открытому уроку «Тестирование API в ASP.NET Core: Интеграция и Нагрузка» 5 августа в 20:00 МСК. На вебинаре мы:
- Разберем ключевые концепции интеграционного и нагрузочного тестирования.
- Научимся писать интеграционные тесты с популярными библиотеками.
- Освоим инструменты для нагрузочного тестирования и анализа результатов.

📗 Этот урок — отличная подготовка к курсу «C# ASP.NET Core разработчик», который стартует совсем скоро. Все участники вебинара получат скидку на обучение!

Записаться на вебинар → https://otus.pw/sqqO/?erid=2W5zFHob1Sg

Реклама. ООО "ОТУС ОНЛАЙН-ОБРАЗОВАНИЕ". ИНН 9705100963.
🧠 Задача: "Сколько раз выполнится блок Console.WriteLine?"


public class Program
{
public static void Main()
{
var obj = new Counter();
for (int i = 0; i < 3; i++)
{
Console.WriteLine(obj++);
}
}
}

public class Counter
{
private int _value = 0;

public static Counter operator ++(Counter c)
{
c._value++;
return c;
}

public static implicit operator int(Counter c)
{
return c._value;
}
}



Вопрос:
Что выведет программа?

🔍 Разбор:

На первый взгляд кажется, что программа выведет:

1
2
3

📎 Но на самом деле она выведет:

0
1
2

Почему?
Оператор obj++ — это постфиксный инкремент. Он:

сначала вызывает implicit operator int для текущего значения,

а затем вызывает operator ++.

То есть порядок такой:

Console.WriteLine(obj++) вызывает implicit int до инкремента

только потом ++ увеличивает значение.

Что это проверяет:

Знание порядка вызова операторов (++ и implicit)

Понимание поведения постфиксных операций в .NET

Умение читать и анализировать перегрузки операторов


@csharp_1001_notes