Как не сломать Dictionary в C# из-за регистра строк?
Очень частая ситуация: ключи приходят из API, заголовков, логинов, команд или конфигов. В одном месте строка может быть
Обычный Dictionary сравнивает строки с учётом регистра:
Результат будет:
Если регистр не должен иметь значения, это нужно указать при создании словаря:
Теперь ключи сравниваются без учёта регистра:
Вывод:
Это полезно для HTTP-заголовков:
Для команд:
И для конфигов, где пользователь может написать ключ в разном регистре:
Лучше задавать comparer сразу при создании словаря. Приводить ключи вручную через
➡️ C# Ready | #совет
Очень частая ситуация: ключи приходят из API, заголовков, логинов, команд или конфигов. В одном месте строка может быть
Admin, в другом admin.Обычный Dictionary сравнивает строки с учётом регистра:
var roles = new Dictionary<string, int>();
roles["Admin"] = 10;
Console.WriteLine(roles.ContainsKey("admin"));
Результат будет:
False
Если регистр не должен иметь значения, это нужно указать при создании словаря:
var roles = new Dictionary<string, int>(
StringComparer.OrdinalIgnoreCase
);
Теперь ключи сравниваются без учёта регистра:
roles["Admin"] = 10;
Console.WriteLine(roles["admin"]);
Вывод:
10
Это полезно для HTTP-заголовков:
var headers = new Dictionary<string, string>(
StringComparer.OrdinalIgnoreCase
);
Для команд:
var commands = new Dictionary<string, Action>(
StringComparer.OrdinalIgnoreCase
);
И для конфигов, где пользователь может написать ключ в разном регистре:
settings["LogLevel"] = "Debug";
Console.WriteLine(settings["loglevel"]);
Лучше задавать comparer сразу при создании словаря. Приводить ключи вручную через
ToLower() хуже: можно забыть сделать это в одном месте и получить странный баг.Please open Telegram to view this post
VIEW IN TELEGRAM
👍10❤6🔥6
This media is not supported in your browser
VIEW IN TELEGRAM
Этот репозиторий — большая коллекция материалов для работы с .NET. Здесь собраны полезные библиотеки, инструменты, фреймворки и проекты, которые помогают быстрее находить решения и лучше ориентироваться.
Оставляю ссылочку: GitHub📱
Please open Telegram to view this post
VIEW IN TELEGRAM
❤9👍7🔥1
Интересная статья о том, как сделать нормальный онбординг в desktop-приложении на C# и Avalonia!
В этой статье:
• Как строятся интерактивные туры по интерфейсу с подсветкой элементов
• Почему desktop-приложениям не хватает готовых onboarding-инструментов
• Как автор решал позиционирование, скрытые элементы, темы и сохранение прогресса
➡️ C# Ready | #статья
В этой статье:
• Как строятся интерактивные туры по интерфейсу с подсветкой элементов
• Почему desktop-приложениям не хватает готовых onboarding-инструментов
• Как автор решал позиционирование, скрытые элементы, темы и сохранение прогресса
Продолжай читать на Habr!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍4🔥2
Почему в C# стоит аккуратно использовать DateTime.Now?
Когда нужно сохранить время события, часто пишут так:
Для вывода пользователю это может быть нормально. Но для логики, хранения и сравнения времени DateTime.Now часто создаёт проблемы.
Он зависит от локального часового пояса машины:
На сервере, в контейнере, на CI и у разработчика время может отличаться.
Для технических событий обычно лучше брать UTC:
Такое значение проще хранить в базе, сравнивать и передавать между сервисами:
Но есть ещё один нюанс, DateTime сам по себе не хранит смещение часового пояса так явно, как DateTimeOffset.
Для событий, где важно зафиксировать момент времени, часто удобнее DateTimeOffset:
Например, для аудита:
А локальное время лучше получать только на границе приложения, перед показом пользователю, в отчёте или интерфейсе:
Если перемешать локальное время и UTC, баги появляются тихо, токены живут дольше, фильтры по датам съезжают, события в логах идут не в том порядке.
➡️ C# Ready | #совет
Когда нужно сохранить время события, часто пишут так:
var createdAt = DateTime.Now;
Для вывода пользователю это может быть нормально. Но для логики, хранения и сравнения времени DateTime.Now часто создаёт проблемы.
Он зависит от локального часового пояса машины:
Console.WriteLine(DateTime.Now);
На сервере, в контейнере, на CI и у разработчика время может отличаться.
Для технических событий обычно лучше брать UTC:
var createdAt = DateTime.UtcNow;
Такое значение проще хранить в базе, сравнивать и передавать между сервисами:
if (DateTime.UtcNow > token.ExpiresAtUtc) {
return false;
}Но есть ещё один нюанс, DateTime сам по себе не хранит смещение часового пояса так явно, как DateTimeOffset.
Для событий, где важно зафиксировать момент времени, часто удобнее DateTimeOffset:
var createdAt = DateTimeOffset.UtcNow;
Например, для аудита:
var entry = new AuditEntry(
userId,
DateTimeOffset.UtcNow
);
А локальное время лучше получать только на границе приложения, перед показом пользователю, в отчёте или интерфейсе:
var local = createdAt.ToLocalTime();
Если перемешать локальное время и UTC, баги появляются тихо, токены живут дольше, фильтры по датам съезжают, события в логах идут не в том порядке.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3👍2🔥1
This media is not supported in your browser
VIEW IN TELEGRAM
Этот репозиторий поможет разобраться с основными паттернами проектирования и понять, как применять их в реальной разработке на C#. Здесь собраны примеры реализации популярных паттернов с понятным кодом и структурой, которая помогает быстро понять идею каждого решения.
Оставляю ссылочку: GitHub
Please open Telegram to view this post
VIEW IN TELEGRAM
❤11👍7🔥4
Шпаргалка по LINQ в C#!
Например, Where() фильтрует коллекцию, Select() преобразует элементы, а GroupBy() собирает данные по ключу.
На картинке LINQ cheat sheet с примерами query syntax и lambda syntax: filtering, anonymous type, ordering, joining, grouping, paging через Skip/Take и базовые операции над последовательностями.
Сохрани, чтобы не потерять!
➡️ C# Ready | #ресурс
Например, Where() фильтрует коллекцию, Select() преобразует элементы, а GroupBy() собирает данные по ключу.
На картинке LINQ cheat sheet с примерами query syntax и lambda syntax: filtering, anonymous type, ordering, joining, grouping, paging через Skip/Take и базовые операции над последовательностями.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤12👍5🔥4
Собираем простой rate limiter для API на C#!
Иногда нужно ограничить частоту действий: попытки входа, запросы к endpoint-у, отправку писем или генерацию кодов. Сделаем маленький in-memory limiter, который разрешает не больше N действий за окно времени.
Создадим модель записи:
Хранить события будем в словаре:
Метод проверки принимает ключ пользователя:
Очистим старые события:
Если лимит уже достигнут, блокируем:
Использование:
Такой вариант подойдёт для одного процесса. Если приложение работает в нескольких инстансах, limiter лучше переносить в Redis или другой общий storage.
➡️ C# Ready | #совет
Иногда нужно ограничить частоту действий: попытки входа, запросы к endpoint-у, отправку писем или генерацию кодов. Сделаем маленький in-memory limiter, который разрешает не больше N действий за окно времени.
Создадим модель записи:
public sealed record Hit(
string Key,
DateTimeOffset CreatedAt
);
Хранить события будем в словаре:
private readonly Dictionary<string, Queue<DateTimeOffset>> _hits = new();
private readonly object _lock = new();
Метод проверки принимает ключ пользователя:
public bool Allow(string key, int limit, TimeSpan window)
{
var now = DateTimeOffset.UtcNow;
var border = now - window;
Очистим старые события:
lock (_lock)
{
if (!_hits.TryGetValue(key, out var queue)) {
queue = new Queue<DateTimeOffset>();
_hits[key] = queue;
}
while (queue.Count > 0 && queue.Peek() < border) {
queue.Dequeue();
}
Если лимит уже достигнут, блокируем:
if (queue.Count >= limit) {
return false;
}
queue.Enqueue(now);
return true;
}
}Использование:
if (!limiter.Allow(userId, 5, TimeSpan.FromMinutes(1))) {
return Results.TooManyRequests();
}Такой вариант подойдёт для одного процесса. Если приложение работает в нескольких инстансах, limiter лучше переносить в Redis или другой общий storage.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤7👍2🔥1
This media is not supported in your browser
VIEW IN TELEGRAM
The C# Academy большой сайт для прокачки C# и .NET через реальные проекты!
На сайте собрана практическая дорожная карта по C# и .NET: консольные приложения, базы данных, ASP.NET Core, Entity Framework, тестирование, API, fullstack-направление и проекты разной сложности. Фишка в том, что это не просто теория, а обучение через задачи и реальные мини-проекты: можно идти по роадмапу, закрывать темы, собирать портфолио и постепенно переходить от базового C# к нормальной backend-разработке на .NET.
➡️ C# Ready | #ресурс
На сайте собрана практическая дорожная карта по C# и .NET: консольные приложения, базы данных, ASP.NET Core, Entity Framework, тестирование, API, fullstack-направление и проекты разной сложности. Фишка в том, что это не просто теория, а обучение через задачи и реальные мини-проекты: можно идти по роадмапу, закрывать темы, собирать портфолио и постепенно переходить от базового C# к нормальной backend-разработке на .NET.
Оставляю ссылочку: The C# Academy
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8🔥6👍4
Как в C# аккуратно ограничить область using?
Иногда объект нужно освободить не в конце всего метода, а сразу после маленького участка кода.
Часто пишут так:
Для короткого ресурса лучше явно ограничить блок:
После блока stream уже закрыт:
Это особенно важно для файлов:
И для сетевых или database-ресурсов, где лишнее удержание соединения может мешать другим операциям:
А обычный
➡️ C# Ready | #совет
Иногда объект нужно освободить не в конце всего метода, а сразу после маленького участка кода.
Часто пишут так:
using var stream = File.OpenRead(path);
var hash = SHA256.HashData(stream);
DoSomethingElse();
using var освободит stream только в конце текущей области видимости. Если метод большой, файл может оставаться открытым дольше, чем нужно.Для короткого ресурса лучше явно ограничить блок:
byte[] hash;
using (var stream = File.OpenRead(path))
{
hash = SHA256.HashData(stream);
}
После блока stream уже закрыт:
DoSomethingElse();
Это особенно важно для файлов:
using (var file = File.OpenWrite("report.txt"))
{
await file.WriteAsync(data);
}И для сетевых или database-ресурсов, где лишнее удержание соединения может мешать другим операциям:
using (var connection = CreateConnection())
{
await connection.OpenAsync();
await SaveAsync(connection);
}
using var удобен, когда ресурс действительно нужен до конца метода:using var activity = tracer.StartActivity("Import");А обычный
using (...) { } лучше, когда ресурс нужен только в маленьком блоке.Please open Telegram to view this post
VIEW IN TELEGRAM
❤9👍3🔥3
Делаем мини-router команд на C#!
Нужно обработать набор текстовых команд без длинного switch? В этой задаче собираем простой router на Dictionary, который находит нужный обработчик и не зависит от регистра введённой команды.
В этой задаче:
Такой подход удобен для CLI-утилит, ботов, админских команд и внутренних инструментов, где набор действий постепенно растёт.
➡️ C# Ready | #задача
Нужно обработать набор текстовых команд без длинного switch? В этой задаче собираем простой router на Dictionary, который находит нужный обработчик и не зависит от регистра введённой команды.
В этой задаче:
• Храним команды в Dictionary;
• Ищем обработчик через TryGetValue;
• Запускаем нужное действие.
Такой подход удобен для CLI-утилит, ботов, админских команд и внутренних инструментов, где набор действий постепенно растёт.
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10❤4👍3
Почему LINQ-запрос может выполниться несколько раз?
LINQ часто выглядит как готовая коллекция:
Но на самом деле Where() возвращает отложенный запрос. Он не выполняется в момент создания, а запускается каждый раз, когда результат начинают перебирать.
Например:
Если пройтись по active два раза, фильтрация тоже выполнится два раза:
Это может быть нормальным поведением для лёгких коллекций, но опасно, если внутри запрос к базе, чтение файла, сетевой вызов или дорогой расчёт.
Когда результат нужен именно как снимок данных, лучше материализовать его явно:
Теперь фильтрация выполнится один раз, а дальше код будет работать уже с готовым списком:
Если результат используется несколько раз, подумай, нужен ли тебе отложенный запрос или готовая коллекция через ToList().
➡️ C# Ready | #совет
LINQ часто выглядит как готовая коллекция:
var active = users.Where(u => u.IsActive);
Но на самом деле Where() возвращает отложенный запрос. Он не выполняется в момент создания, а запускается каждый раз, когда результат начинают перебирать.
Например:
var active = users.Where(u => {
Console.WriteLine(u.Name);
return u.IsActive;
});Если пройтись по active два раза, фильтрация тоже выполнится два раза:
foreach (var user in active) { }
foreach (var user in active) { }Это может быть нормальным поведением для лёгких коллекций, но опасно, если внутри запрос к базе, чтение файла, сетевой вызов или дорогой расчёт.
Когда результат нужен именно как снимок данных, лучше материализовать его явно:
var active = users
.Where(u => u.IsActive)
.ToList();
Теперь фильтрация выполнится один раз, а дальше код будет работать уже с готовым списком:
foreach (var user in active) { }
foreach (var user in active) { }Если результат используется несколько раз, подумай, нужен ли тебе отложенный запрос или готовая коллекция через ToList().
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10❤7🔥3
This media is not supported in your browser
VIEW IN TELEGRAM
Этот репозиторий представляет собой большую коллекцию реализаций алгоритмов и структур данных на языке C#. Здесь можно найти примеры сортировок, поиска, работы со строками, математических алгоритмов, графов, деревьев и других классических задач из программирования.
Оставляю ссылочку: GitHub
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11❤6🔥6
Напоминалка по delegates, events и базовым конструкциям C#!
Например, delegate описывает сигнатуру метода, а event позволяет подписывать обработчики на событие через типизированный делегат.
На картинке пример объявления delegate, регистрация методов, anonymous methods, события, популярные event handler-типы, а также рядом есть краткие блоки по классам, методам, struct, enum, свойствам и циклам.
Сохрани, чтобы не потерять!
➡️ C# Ready | #ресурс
Например, delegate описывает сигнатуру метода, а event позволяет подписывать обработчики на событие через типизированный делегат.
На картинке пример объявления delegate, регистрация методов, anonymous methods, события, популярные event handler-типы, а также рядом есть краткие блоки по классам, методам, struct, enum, свойствам и циклам.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9❤5🔥5
Делаем простой троттлинг действий в C#!
Иногда событие срабатывает слишком часто: ввод в поиске, resize окна, клики по кнопке или обновление фильтра. Чтобы не запускать тяжёлую операцию каждый раз, можно ограничить частоту выполнения.
Для начала создадим время последнего запуска:
И зададим минимальный интервал:
Теперь проверим, можно ли выполнять действие:
Когда действие реально выполняется, обновляем lastRun:
Но удобнее завернуть это в небольшой класс:
Использование получается простым:
Первый вызов выполнится, а второй сразу после него будет пропущен.
Такой троттлинг полезен там, где действие можно выполнять не чаще заданного интервала: отправка аналитики, обновление поиска, повторные клики или защита от слишком частых запросов.
➡️ C# Ready | #практика
Иногда событие срабатывает слишком часто: ввод в поиске, resize окна, клики по кнопке или обновление фильтра. Чтобы не запускать тяжёлую операцию каждый раз, можно ограничить частоту выполнения.
Для начала создадим время последнего запуска:
DateTimeOffset lastRun = DateTimeOffset.MinValue;
И зададим минимальный интервал:
TimeSpan interval = TimeSpan.FromSeconds(2);
Теперь проверим, можно ли выполнять действие:
bool CanRun()
{
return DateTimeOffset.UtcNow - lastRun >= interval;
}
Когда действие реально выполняется, обновляем lastRun:
if (CanRun())
{
lastRun = DateTimeOffset.UtcNow;
Console.WriteLine("search");
}
Но удобнее завернуть это в небольшой класс:
sealed class Throttler
{
private DateTimeOffset _lastRun = DateTimeOffset.MinValue;
private readonly TimeSpan _interval;
public Throttler(TimeSpan interval)
{
_interval = interval;
}
public bool TryRun(Action action)
{
var now = DateTimeOffset.UtcNow;
if (now - _lastRun < _interval)
{
return false;
}
_lastRun = now;
action();
return true;
}
}
Использование получается простым:
var throttler = new Throttler(TimeSpan.FromSeconds(2));
throttler.TryRun(() => Console.WriteLine("send request"));
throttler.TryRun(() => Console.WriteLine("send request"));
Первый вызов выполнится, а второй сразу после него будет пропущен.
Такой троттлинг полезен там, где действие можно выполнять не чаще заданного интервала: отправка аналитики, обновление поиска, повторные клики или защита от слишком частых запросов.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥7❤5👍2