В C# 15 (он же .NET 11 preview) наконец-то появились union-типы — фича, которую ждали годами. Теперь можно объявить тип, который представляет одно из нескольких не связанных между собой значений. Классический пример — Result<T>: он может быть либо успехом с данными, либо ошибкой. Или Option<T> с None и значением. Но union не обязан быть обобщённым: можно объединить любые произвольные типы.
Как это выглядит на практике. Допустим, есть три разных record: Windows, Linux, MacOS. Раньше пришлось бы городить общий базовый класс, хранить object или использовать enum-тэг. Теперь достаточно написать public union SupportedOS(Windows, Linux, MacOS); — и компилятор сам сгенерирует структуру с атрибутом [Union] и интерфейсом IUnion. Работать с таким union надо через switch: компилятор проверяет, что перебраны все варианты, и не требует discard-ветки. Забыл один кейс — получишь warning CS8509.
Чтобы попробовать, нужен .NET 11 SDK preview 2+ (лучше preview 4) и флаг <LangVersion>preview</LangVersion> в .csproj. Приятный бонус: union-типы — фича компилятора, поэтому можно целиться на старые рантаймы, достаточно добавить вспомогательные типы UnionAttribute и IUnion, если их нет (в .NET 11 preview 4 они уже встроены). Из IDE поддерживают Visual Studio Preview и VS Code C# DevKit Insiders; Rider пока ждёт.
За кулисами union — обычная struct c одним полем object? Value. Для каждого кейса генерируется конструктор, который кладёт значение в это поле. Но если union собирает значимые типы (int, bool), каждый раз происходит boxing на кучу, что не всегда приемлемо. Разработчики предусмотрели небоксинговую альтернативу: можно реализовать свои TryGetValue-методы и HasValue. Компилятор заметит их и в switch будет использовать их, а не свойство Value. Такой вариант не обязателен, но полезен на горячих путях.
В планах на будущее — union member providers (члены union определяются в другом типе), closed enums (не надо писать discard в switch) и closed hierarchies (запрет на наследование вне сборки). Эти фичи могут войти в .NET 11, а могут отложить. Пока что даже текущая реализация уже рабочая — можно писать компактные, типобезопасные проверки без ручного boxing и громоздких иерархий.