Представьте: TTL кэша истёк, и сотни запросов одновременно обнаружили пустой кэш. Все ломятся в базу за одним и тем же значением. Это называется cache stampede.
Как бы вы это решили? Какой примитив синхронизации выбрать, чтобы первый запрос шёл в БД, а остальные ждали его результата?
Подумайте и проверьте свой ответ здесь: @csharp_interview_lib
📍 Навигация: Вакансии • Задачи • Собесы
#dotnet_challenge
Please open Telegram to view this post
VIEW IN TELEGRAM
Coverlet — инструмент для измерения покрытия кода в .NET-проектах. Он работает на Windows, macOS и Linux, поддерживает .NET Framework и .NET Core, и умеет считать покрытие по строкам, ветвям и методам.
Без инструмента покрытия вы пишете тесты вслепую. Можно потратить часы на тесты, которые проверяют одно и то же, и совсем не касаться критических участков кода.
Coverlet показывает точную картину: вот этот метод не вызывается ни одним тестом, а вот эта ветка if никогда не выполняется при тестировании.
Как подключить
Есть четыре варианта интеграции. Самый распространённый для современных проектов через VSTest. Он уже включён по умолчанию в шаблоны xUnit-проектов начиная с .NET 8.
Если его нет, добавляем в тестовый проект:
dotnet add package coverlet.collector
Запускаем тесты с флагом сбора покрытия:
dotnet test --collect:"XPlat Code Coverage"
После выполнения в папке TestResults появится файл coverage.cobertura.xml с отчётом.
Второй вариант через MSBuild:
dotnet add package coverlet.msbuild
dotnet test /p:CollectCoverage=true
Итог сразу появится в терминале, а файл coverage.json сохранится в корне тестового проекта.
Третий вариант. Глобальный инструмент командной строки:
dotnet tool install --global coverlet.console
coverlet /path/to/test-assembly.dll --target "dotnet" --targetargs "test /path/to/test-project --no-build"
Четвёртый, самый новый, это интеграция с Microsoft Testing Platform. Подходит для проектов на Microsoft.Testing.Platform, требует .NET 8 и выше:
dotnet add package coverlet.MTP
dotnet test --coverlet
Если вы работаете в Visual Studio на Windows, расширение Fine Code Coverage умеет читать вывод Coverlet и подсвечивать покрытие прямо в редакторе. На macOS есть аналог VSMac-CodeCoverage.
Coverlet не требует сложной настройки, встраивается в стандартный dotnet test, работает на всех платформах и бесплатен.
📍 Навигация: Вакансии • Задачи • Собесы
#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
1👍9❤1
Если конфигурация меняется редко, перезапуск приложения — не проблема. Но когда нужно менять, например, тарифы или флаги в реальном времени, рестарт становится дорогим решением.
IOptionsMonitor<T> позволяет получать актуальные значения сразу после изменения файла конфигурации.Как это работает
IOptionsMonitor<T> следит за изменениями источника конфигурации. При каждом обращении к CurrentValue возвращается актуальное значение. Дополнительно можно подписаться на событие изменения через OnChange:public class DynamicPricingService
{
private readonly IOptionsMonitor<PricingOptions> _options;
public DynamicPricingService(IOptionsMonitor<PricingOptions> options)
{
_options = options;
_options.OnChange(updatedOptions =>
{
Log.Information("Pricing updated: BaseRate={BaseRate}",
updatedOptions.BaseRate);
});
}
public decimal CalculatePrice(decimal distance)
{
var currentOptions = _options.CurrentValue;
return currentOptions.BaseRate + (distance * currentOptions.PerMileRate);
}
}
Каждый вызов
CalculatePrice берёт свежее значение из CurrentValue без рестарта и без ручного сброса кэша. Регистрация в Program.cs:builder.Services.AddOptions<PricingOptions>()
.BindConfiguration("Pricing", binderOptions =>
{
binderOptions.BindNonPublicProperties = false;
binderOptions.ErrorOnUnknownConfiguration = true;
})
.ValidateDataAnnotations();
ErrorOnUnknownConfiguration = true защищает от опечаток в ключах — неизвестное поле в конфиге вызовет ошибку, а не тихо проигнорируется.IOptionsMonitor против IOptionsSnapshot
IOptionsMonitor<T> — синглтон. Одно и то же значение живёт на протяжении всего времени работы приложения и обновляется при изменении файла.IOptionsSnapshot<T> — скоупед. Значение фиксируется один раз на запрос и не меняется до его завершения. Это важно там, где нужна консистентность внутри одного HTTP-запроса — чтобы один и тот же запрос не увидел разные значения конфигурации в начале и в конце обработки.Если сервис живёт в синглтоне, используйте
IOptionsMonitor. Если важна согласованность в рамках запроса, IOptionsSnapshot.📍 Навигация: Вакансии • Задачи • Собесы
#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8🤩3🥱2
Substring и Slice выглядят похоже, но работают принципиально по-разному.
Substring — это
new string(...). Каждый вызов:— выделяет новый объект в хипе
— копирует символы в него
— создаёт нагрузку на GC
Slice не создаёт объектов. Это просто новый указатель + длина поверх той же памяти.
int.Parse(ReadOnlySpan<char>) читает символы напрямую оттуда.Частая ошибка
// Так делать не надо — убивает весь смысл
int id = int.Parse(span.Slice(5, 2).ToString());
ToString() на Span создаёт новую строку. Вернулись к исходной проблеме.Одно правило: если
Substring перед Parse это кандидат на замену. 📍 Навигация: Вакансии • Задачи • Собесы
#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
📰 Дайджест недели
Последний дайджест марта.
— Generative AI for Beginners .NET v2
— Почти год с Copilot Coding Agent в dotnet/runtime
— Пять типичных ошибок при проектировании интеграции с помощью Kafka
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#async_news
Последний дайджест марта.
— Generative AI for Beginners .NET v2
— Почти год с Copilot Coding Agent в dotnet/runtime
— Пять типичных ошибок при проектировании интеграции с помощью Kafka
📍 Навигация: Вакансии • Задачи • Собесы
#async_news
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3
Кажется, что примитивы атомарны. Это не так в смысле видимости между потоками: процессор и компилятор переупорядочивают инструкции, каждое ядро держит своё значение в кэше:
private bool _cacheLoaded;
// Поток A
_cacheLoaded = true;
// Поток B — может прочитать false, даже если A уже записал true
if (!_cacheLoaded) LoadCache(); // загружается дважды, данные затираются
Как это исправить
1.
lock. Подходит для составных операций: «прочитать → изменить → записать» должны выполняться как одно целое:private readonly object _sync = new object();
private int _count;
public void Increment()
{
lock (_sync) { _count++; }
}
2.
volatile. Запрещает кэширование значения в регистре. Не заменяет lock. Только для простого чтения/записи одного поля без зависимостей от других.private volatile bool _cacheLoaded;
3.
Interlocked. Атомарная операция на уровне процессора. Быстрее lock, но только для простых числовых операций:private int _count;
public void Increment()
{
Interlocked.Increment(ref _count);
}
📍 Навигация: Вакансии • Задачи • Собесы
#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
Красивые алгоритмы с хорошей асимптотикой имеют большие константы. O(log n) звучит лучше O(n), но если n=20 — линейный поиск по массиву быстрее бинарного поиска по дереву просто потому, что данные помещаются в кэш процессора и нет накладных расходов на обход структуры.
Допустим, нужно найти обработчик по типу события. Первый импульс это словарь или дерево:
// "Правильное" решение — O(1) lookup
private readonly Dictionary<string, IHandler> _handlers = new()
{
["OrderCreated"] = new OrderCreatedHandler(),
["OrderCancelled"] = new OrderCancelledHandler(),
["OrderShipped"] = new OrderShippedHandler(),
};
// "Наивное" решение — O(n) linear scan
private readonly (string EventType, IHandler Handler)[] _handlers =
[
("OrderCreated", new OrderCreatedHandler()),
("OrderCancelled", new OrderCancelledHandler()),
("OrderShipped", new OrderShippedHandler()),
];
public IHandler? Find(string eventType)
{
foreach (var (type, handler) in _handlers)
if (type == eventType) return handler;
return null;
}
При 5–20 обработчиках линейный массив часто быстрее словаря: данные лежат последовательно в памяти, нет хеширования, нет разыменования указателей, кэш доволен. Dictionary начинает выигрывать при десятках тысяч элементов и только тогда.
Бенчмарк говорит сам за себя:
[MemoryDiagnoser]
public class LookupBenchmark
{
private readonly Dictionary<string, int> _dict;
private readonly (string, int)[] _array;
public LookupBenchmark()
{
var data = Enumerable.Range(0, 10)
.Select(i => ($"key{i}", i))
.ToArray();
_dict = data.ToDictionary(x => x.Item1, x => x.Item2);
_array = data;
}
[Benchmark(Baseline = true)]
public int DictLookup() => _dict["key7"];
[Benchmark]
public int ArrayScan()
{
foreach (var (k, v) in _array)
if (k == "key7") return v;
return -1;
}
}
При n=10 массив зачастую быстрее и не аллоцирует ничего лишнего. Измерьте сами.
Когда измерения при реальной нагрузке показывают, что n действительно большой и растёт. Не раньше. Routing-таблица с 15 маршрутами, валидация с 8 правилами, матчинг по 12 паттернам — всё это «малый n», и простой цикл здесь выиграет у любого красивого решения.
📍 Навигация: Вакансии • Задачи • Собесы
#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍6🤔2👾1
Разработчик с Reddit строит автоматический проигрыватель пластинок с нуля: механику, электронику и прошивку для STM32. Чтобы тестировать и отлаживать железо в процессе разработки, он написал десктопное управляющее приложение на C#.
Приложение позволяет управлять проигрывателем с компьютера, снимать статистику и диагностировать проблемы на лету — по сути, это инструментарий для разработчика железа, написанный на том же языке, что и обычный бизнес-софт.
Для него это первый опыт написания control software для физического железа и судя по его словам, ощущение от того, что код управляет реальным устройством в реальном мире, совершенно другое.
📍 Навигация: Вакансии • Задачи • Собесы
#entry_point
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9❤2
Span<T> это ref struct. А ref struct не может существовать в куче. Это не ограничение реализации, это гарантия безопасности по дизайну.Async-методы компилятор превращает в state machine — объект, который живёт в куче и может приостанавливаться между
await-точками. Локальные переменные такого метода становятся полями этого объекта. Поле типа ref struct в объекте на куче — запрещено. Поэтому компилятор просто не даст использовать Span<T> в async-методе.// Не скомпилируется
async Task ProcessAsync(byte[] data)
{
Span<byte> span = data; // CS4012: Span нельзя использовать в async
await Task.Delay(100);
Process(span);
}
Что происходит под капотом
Компилятор превращает async-метод примерно в это:
// Упрощённо — что генерирует компилятор
private struct ProcessAsyncStateMachine : IAsyncStateMachine
{
public byte[] data;
public Span<byte> span; // ← невозможно: ref struct не может быть полем
public int _state;
// ...
}
Стек фрейм между
await не гарантирован, потому что поток может смениться, метод может возобновиться на другом потоке. Span на стеке к тому моменту уже не существует.Как работать с данными в async-коде
Memory<T> — это то, для чего он и создан. Может жить в куче, передаётся через await, конвертируется в Span в синхронных участках:async Task ProcessAsync(Memory<byte> memory)
{
await Task.Delay(100); // можно
// Span получаем только там, где нет await
Span<byte> span = memory.Span;
Process(span);
}
Паттерн:
Memory<T> для хранения и передачи через async-границы, Span<T> для фактической работы с данными в синхронном контексте.async Task<int> ReadAndProcessAsync(Stream stream)
{
// Memory живёт в куче — await доволен
var buffer = new byte[4096];
Memory<byte> memory = buffer;
int bytesRead = await stream.ReadAsync(memory);
// Переходим в sync-контекст — достаём Span
Span<byte> span = memory.Span[..bytesRead];
return CountNewlines(span);
}
static int CountNewlines(Span<byte> data)
{
int count = 0;
foreach (var b in data)
if (b == '\n') count++;
return count;
}
Коротко
Span<T> — инструмент для горячего пути в синхронном коде. Как только появляется await переходите на Memory<T> и конвертируйте в Span только там, где он нужен непосредственно для вычислений.📍 Навигация: Вакансии • Задачи • Собесы
#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
👍18❤2
🚩 OpenFeature для .NET
Смена провайдера feature flags обычно означает переписывание интеграции. OpenFeature это открытый стандарт под крылом CNCF, который даёт единый vendor-agnostic API: меняете провайдера, меняете одну строчку, код не трогаете.
Установка
Требования: .NET 8+ или .NET Framework 4.6.2+
Минимальный пример:
Флаги с контекстом
Передавайте данные о пользователе/запросе для контекстно-зависимых решений:
Логика вокруг вычисления флага
Добавляйте поведение на любом этапе: до, после, при ошибке, в любом случае.
Встроенный
Реакция на изменения
Подписывайтесь на
Dependency Injection (экспериментально)
Поддержка domain-scoped провайдеров: разные провайдеры для разных частей приложения.
Несколько провайдеров одновременно с разными стратегиями:
- FirstMatchStrategy — первый ненулевой результат
- FirstSuccessfulStrategy — первый успешный, игнорируя ошибки
- ComparisonStrategy — параллельное выполнение + сравнение результатов
Собственный провайдер:
Для ASP.NET Core один раз настроили контекст на входе запроса, и он автоматически попадает во все вычисления флагов в рамках этого запроса:
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#sharp_view
Смена провайдера feature flags обычно означает переписывание интеграции. OpenFeature это открытый стандарт под крылом CNCF, который даёт единый vendor-agnostic API: меняете провайдера, меняете одну строчку, код не трогаете.
Установка
dotnet add package OpenFeature
Требования: .NET 8+ или .NET Framework 4.6.2+
Минимальный пример:
await Api.Instance.SetProviderAsync(new InMemoryProvider());
var client = Api.Instance.GetClient();
bool v2Enabled = await client.GetBooleanValueAsync("v2_enabled", false);
if (v2Enabled)
{
// новая логика
}
Флаги с контекстом
Передавайте данные о пользователе/запросе для контекстно-зависимых решений:
// Глобально
EvaluationContext ctx = EvaluationContext.Builder()
.Set("region", "us-east-1")
.Build();
Api.Instance.SetContext(ctx);
// Или прямо в вызове
bool flagValue = await client.GetBooleanValueAsync(
"some-flag", false, reqCtx);
Логика вокруг вычисления флага
Добавляйте поведение на любом этапе: до, после, при ошибке, в любом случае.
// Глобально для всех вызовов
Api.Instance.AddHooks(new ExampleGlobalHook());
// Только для конкретного клиента
client.AddHooks(new ExampleClientHook());
Встроенный
LoggingHook пишет детальные логи через Microsoft.Extensions.Logging.Реакция на изменения
Api.Instance.AddHandler(
ProviderEventTypes.ProviderReady,
(eventDetails) => Console.WriteLine(eventDetails.Type)
);
Подписывайтесь на
ProviderReady, ProviderError, ProviderConfigurationChanged.Dependency Injection (экспериментально)
dotnet add package OpenFeature.Hosting
builder.Services.AddOpenFeature(featureBuilder => {
featureBuilder
.AddInMemoryProvider()
.AddHook<LoggingHook>();
});Поддержка domain-scoped провайдеров: разные провайдеры для разных частей приложения.
Несколько провайдеров одновременно с разными стратегиями:
- FirstMatchStrategy — первый ненулевой результат
- FirstSuccessfulStrategy — первый успешный, игнорируя ошибки
- ComparisonStrategy — параллельное выполнение + сравнение результатов
var multiProvider = new MultiProvider(providerEntries, new FirstMatchStrategy());
await Api.Instance.SetProviderAsync(multiProvider);
Собственный провайдер:
public class MyProvider : FeatureProvider
{
public override Metadata GetMetadata() =>
new Metadata("My Provider");
public override Task<ResolutionDetails<bool>> ResolveBooleanValueAsync(
string flagKey, bool defaultValue,
EvaluationContext? context = null, ...)
{
// ваша логика
}
// + ResolveString, ResolveInteger, ResolveDouble, ResolveStructure
}
Для ASP.NET Core один раз настроили контекст на входе запроса, и он автоматически попадает во все вычисления флагов в рамках этого запроса:
Api.Instance.SetTransactionContextPropagator(
new AsyncLocalTransactionContextPropagator());
📍 Навигация: Вакансии • Задачи • Собесы
#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
❤10
Microsoft официально объявила: в C# 15 ключевое слово var признаётся устаревшим.
Команда языка ссылается на исследования читаемости кода: оказывается, явное указание типов снижает когнитивную нагрузку на 34% и ускоряет код ревью. Roslyn уже умеет автоматически выводить тип, но теперь хочет, чтобы это делал и программист.
Миграция через
dotnet-upgrade-assistant проставит типы автоматически. Но 40 000 строк кода всё равно ждут вас в ближайшем будущем.📍 Навигация: Вакансии • Задачи • Собесы
Please open Telegram to view this post
VIEW IN TELEGRAM
😁105👏10🤔2🥱1
C# Backend Developer — от 180 000 ₽ гибрид в Санкт-Петербурге
Unity разработчик — до 4 500 €, гибрид в Алматы
Fullstack-разработчик (C# / React Native) — удалёнка или гибрид в Пензе
Please open Telegram to view this post
VIEW IN TELEGRAM
Один из самых распространённых антипаттернов в .NET, который выглядит как хорошая практика, но на деле замедляет систему.
Обычный код:
await Task.Run(() => _logger.LogInformation("Processing..."));
await Task.Run(() => MapToDto(entity));
await Task.Run(() => ValidateHeaders(request));Выглядит современно и async везде.
Что происходит на самом деле
Каждый
Task.Run внутри ASP.NET запроса:— ставит задачу в очередь thread pool
— вызывает context switch
— добавляет scheduling overhead
При этом ASP.NET уже работает на оптимально управляемом thread pool. Вы не освобождаете поток, а создаёте дополнительную нагрузку на планировщик.
Как надо:
// Логирование — всегда синхронно
_logger.LogInformation("Processing...");
// Маппинг — синхронно
var dto = MapToDto(entity);
// Валидация заголовков — синхронно
ValidateHeaders(request);
// async оставляем только для реального I/O
var data = await _repository.GetAsync(id);
var response = await _httpClient.GetAsync(url);
Есть ситуация, где он оправдан: долгая CPU-bound работа, которую нужно вынести за пределы потока запроса, чтобы не блокировать пайплайн.
📍 Навигация: Вакансии • Задачи • Собесы
#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8🥱3
Architectural Decision Records это короткие Markdown-файлы, которые фиксируют контекст, само решение и последствия. Не многостраничная спецификация, а что-то ближе к протоколу встречи. Читается за минуты, но объясняет «почему» лучше любого комментария в коде.
Проблема большинства существующих инструментов для ADR в том, что шаблоны зашиты в сам инструмент. Поменяла команда подход к документированию, нужно ставить другой инструмент.
dotnet-adr это .NET Global Tool, который отделяет сам инструмент от шаблонов. Шаблоны живут как NuGet-пакеты: их можно менять, публиковать свои и раздавать внутри организации через приватный feed.
Установка:
dotnet tool install -g adr
Подключаем стандартный пакет шаблонов:
adr templates package set adr.templates
adr templates package install
Создаём первый ADR:
adr new "Use PostgreSQL instead of MongoDB"
Инструмент создаст нумерованный Markdown-файл с заголовком и структурой из выбранного шаблона. По умолчанию файлы складываются в docs/adr, но путь настраивается через adr.config.json в корне репозитория:
{
"path": "./Docs/Adr"
}Если одно решение заменяет другое, это фиксируется явно:
adr new "Switch to Cosmos DB" -i 3
Третий ADR получит статус «superseded», новый сошлётся на него.
Инструмент подходит тем, кто работает в .NET-экосистеме и хочет хранить архитектурные решения рядом с кодом, не усложняя процесс.
📍 Навигация: Вакансии • Задачи • Собесы
#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍1🥱1
✌🏻 У нас две новости — хорошая и плохая!
Хорошая: Ваших знаний, скорее всего, хватит, чтобы собрать рабочую демку AI-агента в Colab. 🫡
Плохая: Вы вряд ли выведете его в прод, не обанкротившись на токенах и не слив базу. 🤯
Для защиты от таких сценариев мы полностью пересобрали курс «Разработка AI-агентов». Теперь внутри плотная работа с экономикой ресурсов, дебаг через time-travel в
Программа курса, полный состав спикеров и другие подробности 👈🏻
ВАЖНО! До 5 апреля на курс действует скидка, но свободные места могут закончиться раньше.
Хорошая: Ваших знаний, скорее всего, хватит, чтобы собрать рабочую демку AI-агента в Colab. 🫡
Плохая: Вы вряд ли выведете его в прод, не обанкротившись на токенах и не слив базу. 🤯
Для защиты от таких сценариев мы полностью пересобрали курс «Разработка AI-агентов». Теперь внутри плотная работа с экономикой ресурсов, дебаг через time-travel в
LangGraph, извлечение данных из кривых сканов для RAG и комплаенс по 152-ФЗ.Если всё ещё сомневаетесь, послушайте голосовое от спикера курса Влада Прошинского, где он объясняет, как правильно тестировать агентов перед релизом.
Программа курса, полный состав спикеров и другие подробности 👈🏻
ВАЖНО! До 5 апреля на курс действует скидка, но свободные места могут закончиться раньше.
Фрагмент кода выглядит как задача с подвохом. Два метода, оба принимают null. Какой вызовется:
void Print(string text) => Console.WriteLine("String");
void Print(object obj) => Console.WriteLine("Object");
Print(null);Ответ:
Компилятор видит два кандидата.
null совместим и со string, и с object, потому что оба являются ссылочными типами и принимают null. Выбор делается по принципу наибольшей специфичности: из нескольких подходящих перегрузок выбирается та, чей параметр является более производным типом. string наследует от object, значит string более специфичный тип. Это поведение описано в спецификации C# как часть алгоритма разрешения перегрузок. Когда один тип параметра неявно конвертируется в другой, побеждает более конкретный.
Чтобы явно указать нужную перегрузку, достаточно привести
null к нужному типу: Print((object)null); // выведет "Object"
Print((string)null); // выведет "String"
📍 Навигация: Вакансии • Задачи • Собесы
#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
👍28❤2
Большинство проблем с многопоточностью выглядят одинаково везде. Но часть из них появляется только в конкретной среде. WinForms, WPF и ASP.NET имеют свои контексты синхронизации, и если их игнорировать, получаем краш или дедлок там, где вроде бы всё выглядело нормально.
Что идёт не так
В WinForms и WPF UI-компоненты не являются потокобезопасными. Обновлять их можно только из UI-потока. Если фоновый поток попытается напрямую записать что-то в
label.Text или textBox.Value, получаем InvalidOperationException. В WPF для этого используется Dispatcher.BeginInvoke, в WinForms — Control.Invoke или Control.BeginInvoke.Казалось бы, очевидное правило. Но баг всё равно появляется: чаще всего тогда, когда разработчик делает
Task.Run, внутри него обращается к UI, а анализатор это не видит.С
Dispatcher.Invoke другая история. Это синхронный вызов, он блокирует текущий поток до завершения. Если вызвать Invoke из самого UI-потока или из кода, который UI-поток уже ждёт, получаем дедлок. Правило простое: почти всегда нужен BeginInvoke (асинхронный), а не Invoke.Как находить такие баги до прода
Статические анализаторы:
Roslyn, AsyncFixer, Microsoft.VisualStudio.Threading.Analyzers и ThreadSafetyAnalyzer умеют находить прямые обращения к UI из неправильного потока, синхронные блокировки async-методов и потенциальные дедлоки в диспетчере.Подключить их можно через NuGet:
dotnet add package Microsoft.VisualStudio.Threading.Analyzers
dotnet add package AsyncFixer
После подключения анализаторы начинают предупреждать прямо в IDE, до сборки. Часть правил даже предлагает автофикс.
На практике это работает: после включения анализаторов в одном проекте удалось найти три гонки данных ещё до того, как они добрались до пользователей.
📍 Навигация: Вакансии • Задачи • Собесы
#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11❤2
В C# 15 появился ключевой синтаксис union. Он решает давнюю проблему: когда метод должен вернуть одно из нескольких возможных значений, раньше выбор был невелик. object не накладывает никаких ограничений, маркерные интерфейсы нельзя «запечатать», а базовые классы требуют общего предка. Union types убирают все эти ограничения.
Что это и как работает
Union-тип объявляет закрытое множество допустимых типов. Компилятор знает полный список, поэтому проверяет исчерпываемость switch-выражений прямо при сборке.Простейший пример:
public record class Cat(string Name);
public record class Dog(string Name);
public record class Bird(string Name);
public union Pet(Cat, Dog, Bird);
Переменная Pet хранит ровно один из трёх типов. Присваивание работает через неявное преобразование:
Pet pet = new Dog("Rex");
Console.WriteLine(pet.Value); // Dog { Name = Rex }switch по такой переменной не требует ветки default или _. Если вы позже добавите четвёртый тип в объявление union, компилятор выдаст предупреждение в каждом месте, где не хватает обработчика.string name = pet switch
{
Dog d => d.Name,
Cat c => c.Name,
Bird b => b.Name,
};
Фича доступна начиная с .NET 11 Preview 2.
📍 Навигация: Вакансии • Задачи • Собесы
#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
👍33👾3😁1
На технических интервью по C# для джунов и иногда для мидлов вопрос про перегрузки и переопределения задаётся одним из первых.
Оба механизма связаны с методами и их именами. Оба выглядят похоже на первый взгляд. Но работают они в совершенно разных ситуациях и решают разные задачи.
Разница между ними принципиальная. Один работает на уровне компиляции, другой на уровне выполнения программы. Один не требует наследования, другой без него невозможен.
📍 Навигация: Вакансии • Задачи • Собесы
#dotnet_challenge
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2🥱2
Мы уже рассмотрели что такое union-тип, теперь пора подумать где это использовать.
Бывает, что API принимает как одиночное значение, так и коллекцию. Union с телом позволяет добавить вспомогательный метод прямо в объявление:
public union OneOrMore<T>(T, IEnumerable<T>)
{
public IEnumerable<T> AsEnumerable() => Value switch
{
T single => [single],
IEnumerable<T> multiple => multiple,
null => []
};
}
Использование:
OneOrMore<string> tags = "dotnet";
OneOrMore<string> moreTags = new[] { "csharp", "unions", "preview" };
foreach (var tag in tags.AsEnumerable())
Console.Write($"[{tag}] ");
// [dotnet]
Поддержка UnionAttribute и IUnion в рантайме ещё не добавлена, поэтому в Preview 2 нужно вручную добавить в проект небольшой полифил:
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct,
AllowMultiple = false)]
public sealed class UnionAttribute : Attribute;
public interface IUnion
{
object? Value { get; }
}
}
После этого синтаксис union работает в полном объёме.
📍 Навигация: Вакансии • Задачи • Собесы
#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
❤🔥5👍3❤1🔥1