Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Тип объединения представляет значение, которое может быть одним из нескольких типов вариантов. Объединения обеспечивают неявные преобразования из каждого типа регистра, исчерпывающего сопоставления шаблонов и расширенного отслеживания допустимости null. Используйте ключевое union слово для объявления типа объединения:
public union Pet(Cat, Dog, Bird);
Это объявление создает Pet объединение с тремя типами регистров: Cat, Dogи Bird. Для переменной можно назначить любое значение типа регистра Pet . Компилятор гарантирует, что switch выражения охватывают все типы вариантов.
Справочные документы по языку C# описывают последнюю выпущенную версию языка C#. Она также содержит начальную документацию по функциям в общедоступных предварительных версиях для предстоящего языкового выпуска.
Документация определяет любую функцию, впервые представленную в последних трех версиях языка или в текущих общедоступных предварительных версиях.
Подсказка
Чтобы узнать, когда функция впервые появилась в C#, ознакомьтесь со статьей об истории версий языка C#.
Объявите объединение, когда значение должно быть именно одним из фиксированных типов, и компилятор должен принудительно применить все возможности. Ниже приведены распространенные сценарии.
-
Возвращает результат или ошибку: метод возвращает значение успешного выполнения или значение ошибки, а вызывающий объект должен обрабатывать оба. Объединение, например
union Result(Success, Error), делает набор результатов явным. -
Отправка сообщений или команд: система обрабатывает закрытый набор типов сообщений. Объединение гарантирует, что новые типы сообщений создают предупреждения во время компиляции на всех
switchустройствах, которые еще не обрабатывают их. - Замена интерфейсов маркеров или абстрактных базовых классов: если вы используете интерфейс или абстрактный класс исключительно для группирования типов шаблонов для сопоставления шаблонов, объединение обеспечивает исчерпывающую проверку без необходимости наследования или общих членов.
Объединение отличается от других объявлений типов важными способами:
-
classВ отличие от элемента илиstructобъединения, не определяются новые члены данных. Вместо этого он создает существующие типы в закрытый набор альтернативных вариантов. -
interfaceВ отличие от объединения, вы определяете полный список типов вариантов в объявлении, и компилятор использует этот список для проверки исчерпывающей готовности. -
recordВ отличие от объединения, не добавляется равенство, клонирование или деконструкция поведения. Профсоюз фокусируется на "какой случай это?" а не "какие поля у него есть?"
Это важно
В .NET 11 preview 2 среда выполнения не включает UnionAttribute и IUnion интерфейс. Чтобы использовать типы профсоюзов, необходимо объявить их самостоятельно. Сведения о необходимых объявлениях см. в разделе "Реализация союза".
Объявления профсоюза
Объявление объединения указывает имя и список типов вариантов:
public union Pet(Cat, Dog, Bird);
Типы регистров могут быть любым типом, который преобразуется в objectклассы, структуры, интерфейсы, параметры типа, типы, типы, допускающие значение NULL, и другие объединения. В следующих примерах показаны различные возможности типа регистра:
public record class Cat(string Name);
public record class Dog(string Name);
public record class Bird(string Name);
public record class None;
public record class Some<T>(T Value);
public union Option<T>(None, Some<T>);
public union IntOrString(int, string);
Если тип регистра является типом значения (например int), значение задается при хранении в свойстве объединения Value . Профсоюзы хранят содержимое как одну object? ссылку.
Объявление профсоюза может включать тело с дополнительными членами, как и структуру, при условии некоторых ограничений. Объявления объединения не могут включать поля экземпляров, автоматические свойства или события, подобные полям. Вы также не можете объявлять открытые конструкторы с одним параметром, так как компилятор создает эти конструкторы в качестве членов создания объединения:
public union OneOrMore<T>(T, IEnumerable<T>)
{
public IEnumerable<T> AsEnumerable() => Value switch
{
T single => [single],
IEnumerable<T> multiple => multiple,
_ => []
};
}
Преобразования объединения
Неявное преобразование объединения существует из каждого типа регистра в тип объединения. Не нужно явно вызывать конструктор:
static void BasicConversion()
{
Pet pet = new Dog("Rex");
Console.WriteLine(pet.Value); // output: Dog { Name = Rex }
Pet pet2 = new Cat("Whiskers");
Console.WriteLine(pet2.Value); // output: Cat { Name = Whiskers }
}
Преобразования объединения работают путем вызова соответствующего созданного конструктора. Если определяемый пользователем неявный оператор преобразования существует для того же типа, определяемый пользователем оператор имеет приоритет над преобразованием объединения. Дополнительные сведения о приоритете преобразования см. в спецификации языка.
Преобразование объединения в структуру объединения, допускаемую значение NULL (T?), также работает при T использовании типа объединения:
static void NullableUnionExample()
{
Pet? maybePet = new Dog("Buddy");
Pet? noPet = null;
Console.WriteLine(Describe(maybePet)); // output: Dog: Buddy
Console.WriteLine(Describe(noPet)); // output: no pet
static string Describe(Pet? pet) => pet switch
{
Dog d => d.Name,
Cat c => c.Name,
Bird b => b.Name,
null => "no pet",
};
}
Сопоставление объединения
При сопоставлении шаблонов с типом объединения шаблоны применяются к свойству объединения Value , а не самому значению объединения. Это поведение "распакуивания" означает, что объединение прозрачно для сопоставления шаблонов:
static void PatternMatching()
{
Pet pet = new Dog("Rex");
var name = pet switch
{
Dog d => d.Name,
Cat c => c.Name,
Bird b => b.Name,
};
Console.WriteLine(name); // output: Rex
}
Два шаблона являются исключениями из этого правила: var шаблон и шаблон отмены _ применяются к самому значению объединения, а не к его Value свойству. Используется var для записи значения объединения при GetPet() возврате Pet? значения (Nullable<Pet>):
if (GetPet() is var pet) { /* pet is the Pet? value returned from GetPet */ }
В логических шаблонах каждая ветвь следует правилу распаки по отдельности. Следующие тесты шаблона, Pet? которые не равно null , и его Value значение не равно NULL:
GetPet() switch
{
var pet and not null => ..., // 'var pet' captures the Pet?; 'not null' checks Value
}
Замечание
Поскольку шаблоны применяются к Valueшаблону, как pet is Pet правило, не соответствует, так как Pet проверяется на содержимое объединения, а не сам союз.
Сопоставление значений NULL
Для объединения null структур шаблон проверяет, имеет ли Value значение NULL:
static void NullHandling()
{
Pet pet = default;
Console.WriteLine(pet.Value is null); // output: True
var description = pet switch
{
Dog d => d.Name,
Cat c => c.Name,
Bird b => b.Name,
null => "no pet",
};
Console.WriteLine(description); // output: no pet
}
Для союзов на основе классов выполняется успешно, null если ссылка на объединение имеет значение NULL или его Value свойство имеет значение NULL:
Result<string>? result = null;
if (result is null) { /* true — the reference is null */ }
Result<string> empty = new Result<string>((string?)null);
if (empty is null) { /* true — Value is null */ }
Для типов структур объединения, допускающих значение NULL,Pet? выполняется успешно, null если оболочка, допускаемая значение NULL, не имеет значения или если базовый союз Value имеет значение NULL.
Исчерпывающее объединение
Выражение switch является исчерпывающим при обработке всех типов вариантов объединения. Компилятор предупреждает, только если тип дела не обрабатывается. Вам не нужно включать шаблон отмены (_) или var шаблон, чтобы соответствовать любому типу:
static void PatternMatching()
{
Pet pet = new Dog("Rex");
var name = pet switch
{
Dog d => d.Name,
Cat c => c.Name,
Bird b => b.Name,
};
Console.WriteLine(name); // output: Rex
}
Если значение NULL свойства объединения Value равно "может быть null", необходимо также обрабатывать null , чтобы избежать предупреждения:
static void NullHandling()
{
Pet pet = default;
Console.WriteLine(pet.Value is null); // output: True
var description = pet switch
{
Dog d => d.Name,
Cat c => c.Name,
Bird b => b.Name,
null => "no pet",
};
Console.WriteLine(description); // output: no pet
}
Нуллабельность
Компилятор отслеживает состояние NULL свойства объединения Value с помощью следующих правил:
- При создании значения объединения из типа регистра (с помощью конструктора или преобразования объединения)
Valueполучает значение NULL для входящего значения. - Когда шаблон
HasValueдоступа, отличный от поля, запрашиваетTryGetValue(...)содержимое объединения, состояниеValueNULL становится "не null" вtrueветви.
Пользовательские типы объединения
Компилятор преобразует union объявление в struct объявление. Структура помечается IUnion атрибутом[System.Runtime.CompilerServices.Union], реализует интерфейс. Он включает открытый конструктор и неявное преобразование для каждого типа регистра вместе со свойством Value . Эта сформированная форма считается мнением. Это всегда структуру, всегда поля типов значений и всегда сохраняет содержимое как object?.
Если требуется другое поведение, например объединение на основе классов, настраиваемая стратегия хранения, поддержка взаимодействия или если вы хотите адаптировать существующий тип, можно создать тип объединения вручную.
Любой класс или структура с [Union] атрибутом является типом объединения , если он соответствует базовому шаблону объединения. Для базового шаблона объединения требуется:
- Атрибут
[Union]типа. - Один или несколько открытых конструкторов, каждый из которых имеет одно значение или
inпараметр. Тип параметра каждого конструктора определяет тип регистра. - Общедоступное
Valueсвойство типаobject?(илиobject) с методомgetдоступа.
Все члены профсоюза должны быть публичными. Компилятор использует эти члены для реализации преобразований объединения, сопоставления шаблонов и исчерпывающих проверок. Вы также можете реализовать шаблон доступа, отличный от бокса , или создать тип объединения на основе классов.
Компилятор предполагает, что типы пользовательских союзов удовлетворяют этим правилам поведения:
-
Звук:
Valueвсегда возвращаетnullили значение одного из типов регистра — никогда не является значением другого типа. Для структур профсоюзовdefaultпроизводится .Valuenull -
Стабильность. Если вы создаете значение объединения из типа регистра,
Valueсовпадает с этим типом регистра (или еслиnullвходные данные былиnull). - Эквивалентность создания: если значение неявно преобразуется в два разных типа регистра, оба элемента создания создают одно и то же наблюдаемое поведение.
-
Согласованность шаблонов доступа: элементы
HasValueиTryGetValueчлены, если они присутствуют, ведут себя аналогично проверкеValueнапрямую.
В следующем примере показан пользовательский тип объединения:
[System.Runtime.CompilerServices.Union]
public struct Shape : System.Runtime.CompilerServices.IUnion
{
private readonly object? _value;
public Shape(Circle value) { _value = value; }
public Shape(Rectangle value) { _value = value; }
public object? Value => _value;
}
public record class Circle(double Radius);
public record class Rectangle(double Width, double Height);
static void ManualUnionExample()
{
Shape shape = new Shape(new Circle(5.0));
var area = shape switch
{
Circle c => Math.PI * c.Radius * c.Radius,
Rectangle r => r.Width * r.Height,
};
Console.WriteLine($"{area:F2}"); // output: 78.54
}
Шаблон доступа без бокса
Настраиваемый тип объединения может при необходимости реализовать шаблон доступа, отличный от бокса , чтобы обеспечить строго типизированный доступ к вариантам типа значений без бокса во время сопоставления шаблонов. Для этого шаблона требуется:
-
HasValueСвойство типаbool, которое возвращаетсяtrue, когдаValueэто неnullтак. -
TryGetValueМетод для каждого типа регистра, который возвращаетboolи передает значение черезoutпараметр.
[System.Runtime.CompilerServices.Union]
public struct IntOrBool : System.Runtime.CompilerServices.IUnion
{
private readonly int _intValue;
private readonly bool _boolValue;
private readonly byte _tag; // 0 = none, 1 = int, 2 = bool
public IntOrBool(int? value)
{
if (value.HasValue)
{
_intValue = value.Value;
_tag = 1;
}
}
public IntOrBool(bool? value)
{
if (value.HasValue)
{
_boolValue = value.Value;
_tag = 2;
}
}
public object? Value => _tag switch
{
1 => _intValue,
2 => _boolValue,
_ => null
};
public bool HasValue => _tag != 0;
public bool TryGetValue(out int value)
{
value = _intValue;
return _tag == 1;
}
public bool TryGetValue(out bool value)
{
value = _boolValue;
return _tag == 2;
}
}
static void NonBoxingExample()
{
IntOrBool val = new IntOrBool((int?)42);
var description = val switch
{
int i => $"int: {i}",
bool b => $"bool: {b}",
};
Console.WriteLine(description); // output: int: 42
}
Компилятор предпочитает TryGetValueValue свойство при реализации сопоставления шаблонов, что позволяет избежать типов значений бокса.
Типы объединения на основе классов
Класс также может быть типом объединения. Этот тип объединения полезен при необходимости ссылочной семантики или наследования:
[System.Runtime.CompilerServices.Union]
public class Result<T> : System.Runtime.CompilerServices.IUnion
{
private readonly object? _value;
public Result(T? value) { _value = value; }
public Result(Exception? value) { _value = value; }
public object? Value => _value;
}
static void ClassUnionExample()
{
Result<string> ok = new Result<string>("success");
Result<string> err = new Result<string>(new InvalidOperationException("failed"));
Console.WriteLine(Describe(ok)); // output: OK: success
Console.WriteLine(Describe(err)); // output: Error: failed
static string Describe(Result<string> result) => result switch
{
string s => $"OK: {s}",
Exception e => $"Error: {e.Message}",
null => "null",
};
}
Для союзов на основе классов шаблон соответствует как ссылке NULL, null так и значению NULL Value.
Реализация объединения
Следующие атрибуты и интерфейс поддерживают типы союзов во время компиляции и среде выполнения:
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false)]
public sealed class UnionAttribute : Attribute;
public interface IUnion
{
object? Value { get; }
}
}
Объявления объединения, созданные компилятором IUnion. Вы можете проверить любое значение объединения во время выполнения с помощью IUnion:
if (value is IUnion { Value: null }) { /* the union's value is null */ }
При объявлении union типа компилятор создает структуру, реализующую IUnion. Например, Pet объявление (public union Pet(Cat, Dog, Bird);) становится эквивалентным:
[Union] public struct Pet : IUnion
{
public Pet(Cat value) => Value = value;
public Pet(Dog value) => Value = value;
public Pet(Bird value) => Value = value;
public object? Value { get; }
}
Это важно
В .NET 11 Preview 2 эти типы не включены в среду выполнения. Чтобы использовать типы профсоюзов, их необходимо объявить в проекте. Они будут включены в будущую предварительную версию .NET.
Спецификация языка C#
Дополнительные сведения см. в спецификации компонентов Профсоюзов .