Как индексация строк изменила систему типов HydraScript!
Автор добавил длину и обращение по индексу в интерпретируемый язык на C#. Работа над строками показала, что правила операций удобнее хранить рядом с типами, а не разносить по семантическому анализатору.
В статье разбирают:
• как интерфейс IOperator описывает допустимость операции и тип результата
• почему строки и массивы могут использовать общие правила индексации
• где проходит граница между проверкой типов и выполнением выражения
Такой подход упрощает развитие анализатора, но оставляет диагностику ошибок и контекст исходного кода в соответствующем слое.
➡️ C# Ready | #статья
Автор добавил длину и обращение по индексу в интерпретируемый язык на C#. Работа над строками показала, что правила операций удобнее хранить рядом с типами, а не разносить по семантическому анализатору.
В статье разбирают:
• как интерфейс IOperator описывает допустимость операции и тип результата
• почему строки и массивы могут использовать общие правила индексации
• где проходит граница между проверкой типов и выполнением выражения
Такой подход упрощает развитие анализатора, но оставляет диагностику ошибок и контекст исходного кода в соответствующем слое.
Продолжай читать на Habr!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5🔥4👍3
Запускаем периодическую проверку без наложения запросов!
Обычный таймер может вызвать следующий callback, пока предыдущая проверка ещё ждёт сеть. Сегодня соберём последовательный polling через PeriodicTimer и передадим отмену внутрь операции.
Создаём таймер с нужным интервалом:
PeriodicTimer допускает одного ожидающего потребителя. Если очередная проверка длится дольше интервала, накопившиеся тики не создают параллельные копии запроса.
Ждём тик и завершаем цикл через cancellation token:
Следующая итерация начнётся только после завершения RefreshAsync. Поэтому медленный ответ увеличит фактический интервал, но проверки не наложатся друг на друга.
Внутри проверки передаём тот же token сетевому вызову:
При остановке фоновой службы token прервёт ожидание тика и поддерживающий отмену HTTP-запрос.
➡️ C# Ready | #практика
Обычный таймер может вызвать следующий callback, пока предыдущая проверка ещё ждёт сеть. Сегодня соберём последовательный polling через PeriodicTimer и передадим отмену внутрь операции.
Создаём таймер с нужным интервалом:
using var timer = new PeriodicTimer(
TimeSpan.FromSeconds(15));
PeriodicTimer допускает одного ожидающего потребителя. Если очередная проверка длится дольше интервала, накопившиеся тики не создают параллельные копии запроса.
Ждём тик и завершаем цикл через cancellation token:
while (await timer.WaitForNextTickAsync(token))
{
await RefreshAsync(token);
}
Следующая итерация начнётся только после завершения RefreshAsync. Поэтому медленный ответ увеличит фактический интервал, но проверки не наложатся друг на друга.
Внутри проверки передаём тот же token сетевому вызову:
using var response = await client.GetAsync(
endpoint, token);
response.EnsureSuccessStatusCode();
При остановке фоновой службы token прервёт ожидание тика и поддерживающий отмену HTTP-запрос.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8👍4🔥4
Как код C# превращается в машинные инструкции!
Схема показывает весь путь от исходного файла до выполнения процессором. Компилятор C# сначала создаёт промежуточный IL и сборку, а не готовые инструкции для конкретного процессора.
При запуске CLR загружает сборку. Когда вызывается метод, JIT-компилятор переводит его IL в машинный код для текущей платформы.
Сохрани, чтобы не потерять!
➡️ C# Ready | #ресурс
Схема показывает весь путь от исходного файла до выполнения процессором. Компилятор C# сначала создаёт промежуточный IL и сборку, а не готовые инструкции для конкретного процессора.
При запуске CLR загружает сборку. Когда вызывается метод, JIT-компилятор переводит его IL в машинный код для текущей платформы.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10❤4🔥3
Знаете, почему регулярное выражение может надолго занять поток?
При неудачном совпадении шаблоны с вложенными повторами могут проверять множество вариантов. На пользовательском вводе такая обратная проверка способна надолго загрузить процессор:
По умолчанию Regex может работать без ограничения времени. Для обычного движка задаём конечный timeout при создании объекта:
Если поиск не уложится в интервал, будет выброшено RegexMatchTimeoutException. Обрабатываем её отдельно и возвращаем понятную ошибку проверки:
В .NET 7 и новее можно рассмотреть RegexOptions.NonBacktracking, если шаблон не использует неподдерживаемые конструкции вроде lookaround или backreference.
➡️ C# Ready | #совет
При неудачном совпадении шаблоны с вложенными повторами могут проверять множество вариантов. На пользовательском вводе такая обратная проверка способна надолго загрузить процессор:
var pattern = "^(a+)+$";
var regex = new Regex(pattern);
По умолчанию Regex может работать без ограничения времени. Для обычного движка задаём конечный timeout при создании объекта:
var regex = new Regex(pattern, RegexOptions.None,
TimeSpan.FromMilliseconds(100));
Если поиск не уложится в интервал, будет выброшено RegexMatchTimeoutException. Обрабатываем её отдельно и возвращаем понятную ошибку проверки:
try {
return regex.IsMatch(input);
} catch (RegexMatchTimeoutException) {
throw new ValidationException(
"Regex validation timed out");
}В .NET 7 и новее можно рассмотреть RegexOptions.NonBacktracking, если шаблон не использует неподдерживаемые конструкции вроде lookaround или backreference.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤4🔥3
Добавляем замер времени HTTP-запросов через DelegatingHandler!
Когда несколько сервисов обращаются к внешним API, можно видеть статус и длительность каждого запроса. Вынесем замер в обработчик HttpClient, чтобы не дублировать Stopwatch в каждом месте вызова.
Создаём обработчик, который получает логгер через конструктор:
DelegatingHandler передаёт запрос следующему обработчику цепочки. Параметр primary constructor доступен методам класса, поэтому отдельное поле для log не требуется.
Переопределяем асинхронную отправку и сохраняем контракт базового класса:
Токен отмены передаём дальше без замены, чтобы отмена вызывающего кода дошла до сетевого запроса.
Перед отправкой запускаем секундомер и готовим поле статуса:
Переменная status остаётся null, если транспорт завершился исключением до получения ответа. Это позволяет логировать и успешные ответы, и неудачные попытки соединения.
Выполняем запрос через следующий обработчик и запоминаем статус:
Ответ возвращается вызывающему коду без чтения или изменения его тела. Блок finally выполняется и после ответа, и при сетевом исключении.
В finally записываем метод, статус и длительность:
Регистрируем обработчик как transient и добавляем его в именованный клиент:
Фабрика создаёт цепочки обработчиков для клиентов, поэтому один экземпляр DelegatingHandler нельзя переиспользовать в нескольких цепочках.
➡️ C# Ready | #практика
Когда несколько сервисов обращаются к внешним API, можно видеть статус и длительность каждого запроса. Вынесем замер в обработчик HttpClient, чтобы не дублировать Stopwatch в каждом месте вызова.
Создаём обработчик, который получает логгер через конструктор:
sealed class TimingHandler(
ILogger<TimingHandler> log) :
DelegatingHandler
{
}
DelegatingHandler передаёт запрос следующему обработчику цепочки. Параметр primary constructor доступен методам класса, поэтому отдельное поле для log не требуется.
Переопределяем асинхронную отправку и сохраняем контракт базового класса:
protected override
Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken token)
Токен отмены передаём дальше без замены, чтобы отмена вызывающего кода дошла до сетевого запроса.
Перед отправкой запускаем секундомер и готовим поле статуса:
var timer = Stopwatch.StartNew();
HttpStatusCode? status = null;
Переменная status остаётся null, если транспорт завершился исключением до получения ответа. Это позволяет логировать и успешные ответы, и неудачные попытки соединения.
Выполняем запрос через следующий обработчик и запоминаем статус:
try
{
var response = await base.SendAsync(
request, token);
status = response.StatusCode;
return response;
}
Ответ возвращается вызывающему коду без чтения или изменения его тела. Блок finally выполняется и после ответа, и при сетевом исключении.
В finally записываем метод, статус и длительность:
finally
{
log.LogInformation(
"{Method} {Status} {Ms}ms",
request.Method, status,
timer.ElapsedMilliseconds);
}
Регистрируем обработчик как transient и добавляем его в именованный клиент:
builder.Services.AddTransient<TimingHandler>();
builder.Services.AddHttpClient("catalog")
.AddHttpMessageHandler<TimingHandler>();
Фабрика создаёт цепочки обработчиков для клиентов, поэтому один экземпляр DelegatingHandler нельзя переиспользовать в нескольких цепочках.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍4🔥3
Почему Task.Run не делает синхронный ввод-вывод асинхронным?
Оборачивать блокирующее чтение файла в Task.Run кажется способом освободить текущий поток, но ожидание всё равно занимает поток из пула:
Операция остаётся синхронной. Мы лишь переносим блокирующее ожидание на другой поток, поэтому под нагрузкой такие обёртки могут быстрее исчерпать пул.
Для чтения файла используем API с настоящей асинхронной операцией:
Пока система ждёт данные, рабочий поток не занят синхронным чтением. Переданный токен позволяет отменить операцию, если результат больше не нужен.
Токен отмены передаём и в сам метод чтения, и дальше по цепочке:
Для CPU-вычислений Task.Run может быть уместен, например чтобы не блокировать UI-поток:
Синхронная функция должна сама проверять отмену, потому что токен не прерывает уже запущенный код:
Независимые операции с асинхронным API запускаем напрямую и ожидаем вместе:
Так ожидание I/O не расходует потоки на блокировку. В серверном коде Task.Run вокруг обычного ввода-вывода чаще добавляет переключение потоков, а не пропускную способность.
➡️ C# Ready | #совет
Оборачивать блокирующее чтение файла в Task.Run кажется способом освободить текущий поток, но ожидание всё равно занимает поток из пула:
await Task.Run(() => File.ReadAllText(path));
Операция остаётся синхронной. Мы лишь переносим блокирующее ожидание на другой поток, поэтому под нагрузкой такие обёртки могут быстрее исчерпать пул.
Для чтения файла используем API с настоящей асинхронной операцией:
string text = await File.ReadAllTextAsync(
path, cancellationToken);
Пока система ждёт данные, рабочий поток не занят синхронным чтением. Переданный токен позволяет отменить операцию, если результат больше не нужен.
Токен отмены передаём и в сам метод чтения, и дальше по цепочке:
await using var stream = File.OpenRead(path);
using var reader = new StreamReader(stream);
string text = await reader.ReadToEndAsync(token);
Для CPU-вычислений Task.Run может быть уместен, например чтобы не блокировать UI-поток:
var result = await Task.Run(
() => Calculate(input, token), token);
Синхронная функция должна сама проверять отмену, потому что токен не прерывает уже запущенный код:
foreach (var item in items)
{
token.ThrowIfCancellationRequested();
Process(item);
}
Независимые операции с асинхронным API запускаем напрямую и ожидаем вместе:
Task<string> config = File.ReadAllTextAsync(
configPath);
Task<string> data = File.ReadAllTextAsync(
dataPath);
await Task.WhenAll(config, data);
Так ожидание I/O не расходует потоки на блокировку. В серверном коде Task.Run вокруг обычного ввода-вывода чаще добавляет переключение потоков, а не пропускную способность.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5👍4🔥2
Строим порядок сборки из зависимостей!
Есть список задач и связи между ними. Нужно определить, в каком порядке их можно выполнять, а при циклической зависимости вернуть понятную ошибку.
В этой задаче:
Алгоритм Кана проходит каждую задачу и каждую связь один раз.
➡️ C# Ready | #задача
Есть список задач и связи между ними. Нужно определить, в каком порядке их можно выполнять, а при циклической зависимости вернуть понятную ошибку.
В этой задаче:
• строим граф зависимостей и считаем входящие связи
• обрабатываем готовые задачи через очередь
• проверяем, что в графе не осталось цикла
Алгоритм Кана проходит каждую задачу и каждую связь один раз.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6❤3🔥3
Строим ограниченную очередь фоновых задач через Channel<T>!
Если задания поступают быстрее, чем обрабатываются, неограниченная очередь продолжает расти и расходует память. Bounded channel задаёт верхний предел, а производитель асинхронно ждёт свободное место.
Создаём очередь на 32 задания:
Режим Wait не отбрасывает новые элементы при заполнении. Вместо этого WriteAsync приостанавливает производителя до появления свободного слота.
Передаём задания с поддержкой отмены:
Токен позволяет отменить ожидание записи, если приложение завершает операцию. В реальном producer-коде задания обычно записываются в цикле, а ошибки производства обрабатываются отдельно.
Потребитель читает элементы по мере появления:
ReadAllAsync ждёт новые элементы и завершает перечисление, когда writer закрыт и очередь опустела. Один такой цикл обрабатывает задания последовательно, сохраняя порядок чтения.
Для нескольких производителей можно завершить запись после их окончания:
TryComplete закрывает очередь для новых записей, но позволяет обработать уже добавленные задания. Если producer завершился с исключением, его можно передать в TryComplete(error), чтобы consumer увидел причину остановки.
Запускаем производство и чтение вместе:
Ёмкость 32 ограничивает только число ожидающих элементов, но не время обработки.
➡️ C# Ready | #практика
Если задания поступают быстрее, чем обрабатываются, неограниченная очередь продолжает расти и расходует память. Bounded channel задаёт верхний предел, а производитель асинхронно ждёт свободное место.
Создаём очередь на 32 задания:
var queue = Channel.CreateBounded<Job>(
new BoundedChannelOptions(32)
{ FullMode = BoundedChannelFullMode.Wait });
Режим Wait не отбрасывает новые элементы при заполнении. Вместо этого WriteAsync приостанавливает производителя до появления свободного слота.
Передаём задания с поддержкой отмены:
await queue.Writer.WriteAsync(job, token);
await queue.Writer.WriteAsync(nextJob, token);
Токен позволяет отменить ожидание записи, если приложение завершает операцию. В реальном producer-коде задания обычно записываются в цикле, а ошибки производства обрабатываются отдельно.
Потребитель читает элементы по мере появления:
await foreach (var job in queue.Reader
.ReadAllAsync(token))
{
await HandleAsync(job, token);
}
ReadAllAsync ждёт новые элементы и завершает перечисление, когда writer закрыт и очередь опустела. Один такой цикл обрабатывает задания последовательно, сохраняя порядок чтения.
Для нескольких производителей можно завершить запись после их окончания:
try { await ProduceAsync(queue.Writer, token); }
finally { queue.Writer.TryComplete(); }TryComplete закрывает очередь для новых записей, но позволяет обработать уже добавленные задания. Если producer завершился с исключением, его можно передать в TryComplete(error), чтобы consumer увидел причину остановки.
Запускаем производство и чтение вместе:
var consumer = ConsumeAsync(queue.Reader, token);
Exception? error = null;
try { await ProduceAsync(queue.Writer, token); }
catch (Exception ex) { error = ex; }
queue.Writer.TryComplete(error);
await consumer;
Ёмкость 32 ограничивает только число ожидающих элементов, но не время обработки.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍4🔥2
Шпаргалка по class, record и struct в C#!
Например, class и record class являются ссылочными типами, а struct и record struct передают значения как отдельные данные.
На карточках сравниваются равенство, присваивание и копирование через with. Отдельно показано, почему with не создаёт глубокую копию вложенного списка.
Сохрани, чтобы не потерять!
➡️ C# Ready | #шпора
Например, class и record class являются ссылочными типами, а struct и record struct передают значения как отдельные данные.
На карточках сравниваются равенство, присваивание и копирование через with. Отдельно показано, почему with не создаёт глубокую копию вложенного списка.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥17👍8❤4🤝4
Почему ValueTask не стоит использовать ради ускорения?
ValueTask часто воспринимают как более быстрый Task. На практике он полезен не всегда и может сделать код сложнее.
Главный сценарий для ValueTask появляется, когда результат часто уже готов синхронно. Например, значение нашлось в памяти и не нужно создавать полноценный Task.
Такой метод может вернуть готовое значение без лишней аллокации:
А если значения нет, можно перейти к обычной async-операции:
Но у ValueTask есть ограничения. Его нельзя бездумно ждать несколько раз, сохранять на потом или передавать по цепочке как обычный Task.
Плохой пример выглядит так:
С Task такое обычно безопасно, а с ValueTask можно получить неожиданное поведение, особенно если внутри используется IValueTaskSource.
Если метод почти всегда делает настоящий async I/O, лучше оставить Task:
ValueTask используйте только там, где на то есть причина и польза.
➡️ C# Ready | #совет
ValueTask часто воспринимают как более быстрый Task. На практике он полезен не всегда и может сделать код сложнее.
Главный сценарий для ValueTask появляется, когда результат часто уже готов синхронно. Например, значение нашлось в памяти и не нужно создавать полноценный Task.
Такой метод может вернуть готовое значение без лишней аллокации:
if (cache.TryGetValue(id, out var user))
{
return new ValueTask(user);
}
А если значения нет, можно перейти к обычной async-операции:
return new ValueTask(LoadAsync(id));
Но у ValueTask есть ограничения. Его нельзя бездумно ждать несколько раз, сохранять на потом или передавать по цепочке как обычный Task.
Плохой пример выглядит так:
var result = GetUserAsync(id);
await result;
await result;
С Task такое обычно безопасно, а с ValueTask можно получить неожиданное поведение, особенно если внутри используется IValueTaskSource.
Если метод почти всегда делает настоящий async I/O, лучше оставить Task:
Task LoadUserAsync(int id)
{
return db.Users.FindAsync(id).AsTask();
}
ValueTask используйте только там, где на то есть причина и польза.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍2🔥2
Media is too big
VIEW IN TELEGRAM
CSharpGuide по C# и .NET!
Каталог разбит на шесть этапов, основы языка, управление потоком, методы и коллекции, ООП, продвинутый C# и практический .NET. Внутри этапов собраны отдельные уроки по переменным, LINQ, async/await, JSON, тестированию и другим темам.
В уроках есть примеры кода, которые запускаются прямо в браузере, и последовательные переходы между темами.
➡️ C# Ready | #ресурс
Каталог разбит на шесть этапов, основы языка, управление потоком, методы и коллекции, ООП, продвинутый C# и практический .NET. Внутри этапов собраны отдельные уроки по переменным, LINQ, async/await, JSON, тестированию и другим темам.
В уроках есть примеры кода, которые запускаются прямо в браузере, и последовательные переходы между темами.
Оставляю ссылочку на CSharpGuide
Please open Telegram to view this post
VIEW IN TELEGRAM
❤10👍9🔥4