Библиотека шарписта | C#, F#, .NET, ASP.NET
22K subscribers
2.71K photos
41 videos
85 files
5.08K links
Все самое полезное для C#-разработчика в одном канале.

По рекламе: @proglib_adv

Учиться у нас: https://proglib.io/w/b60af5a4

Для обратной связи: @proglibrary_feeedback_bot

РКН: https://gosuslugi.ru/snet/67a5c81cdc130259d5b7fead
Download Telegram
✏️ Задание: не положить базу после истечения TTL

Представьте: TTL кэша истёк, и сотни запросов одновременно обнаружили пустой кэш. Все ломятся в базу за одним и тем же значением. Это называется cache stampede.

Как бы вы это решили? Какой примитив синхронизации выбрать, чтобы первый запрос шёл в БД, а остальные ждали его результата?

Подумайте и проверьте свой ответ здесь: @csharp_interview_lib

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#dotnet_challenge
Please open Telegram to view this post
VIEW IN TELEGRAM
⚙️ Покрытие кода для .NET

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👍91
⚡️ Рестарт ради смены настроек это лишнее

Если конфигурация меняется редко, перезапуск приложения — не проблема. Но когда нужно менять, например, тарифы или флаги в реальном времени, рестарт становится дорогим решением. 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 и 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
👨‍💻 Разделяемое состояние в многопоточке

Кажется, что примитивы атомарны. Это не так в смысле видимости между потоками: процессор и компилятор переупорядочивают инструкции, каждое ядро держит своё значение в кэше:
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
💡 Красивые алгоритмы медленны при малом n

Красивые алгоритмы с хорошей асимптотикой имеют большие константы. 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
👍92
🚫 Span<T> и async несовместимы

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
👍182
🚩 OpenFeature для .NET

Смена провайдера 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
⚡️ Никаких больше var

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) — удалёнка или гибрид в Пензе

➡️ Еще больше топовых вакансий — в нашем канале C# Jobs

🐸 Библиотека шарписта
Please open Telegram to view this post
VIEW IN TELEGRAM
📎 Task.Run внутри ASP.NET пайплайна

Один из самых распространённых антипаттернов в .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 в LangGraph, извлечение данных из кривых сканов для RAG и комплаенс по 152-ФЗ.

Если всё ещё сомневаетесь, послушайте голосовое от спикера курса Влада Прошинского, где он объясняет, как правильно тестировать агентов перед релизом.


Программа курса, полный состав спикеров и другие подробности 👈🏻

ВАЖНО! До 5 апреля на курс действует скидка, но свободные места могут закончиться раньше.
👀 Разрешение перегрузок в C#

Фрагмент кода выглядит как задача с подвохом. Два метода, оба принимают null. Какой вызовется:
void Print(string text) => Console.WriteLine("String");
void Print(object obj) => Console.WriteLine("Object");

Print(null);


Ответ: выведется "String". И это не случайность, а предсказуемое поведение разрешения перегрузок в C#.

Компилятор видит два кандидата. 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
👍262
👨‍💻 Баги, которые уничтожат ваших пользователей

Большинство проблем с многопоточностью выглядят одинаково везде. Но часть из них появляется только в конкретной среде. 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
👍112
👀 C# 15 Union Types: наконец-то закрытые типы в языке

В 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🥱1