Nota
O acceso a esta páxina require autorización. Pode tentar iniciar sesión ou modificar os directorios.
O acceso a esta páxina require autorización. Pode tentar modificar os directorios.
Un tipo de unión representa un valor que puede ser uno de varios tipos de casos. Las uniones proporcionan conversiones implícitas de cada tipo de caso, coincidencia exhaustiva de patrones y seguimiento mejorado de nulabilidad. Use la union palabra clave para declarar un tipo de unión:
public union Pet(Cat, Dog, Bird);
Esta declaración crea una Pet unión con tres tipos de casos: Cat, Dogy Bird. Puede asignar cualquier valor de tipo de caso a una Pet variable. El compilador garantiza que switch las expresiones cubran todos los tipos de mayúsculas y minúsculas.
La documentación de referencia del lenguaje C# cubre la versión más reciente publicada del lenguaje C#. También contiene documentación inicial sobre las características de las versiones preliminares públicas de la próxima versión del lenguaje.
La documentación identifica cualquier característica introducida por primera vez en las últimas tres versiones del idioma o en las versiones preliminares públicas actuales.
Sugerencia
Para buscar cuándo se introdujo por primera vez una característica en C#, consulte el artículo sobre el historial de versiones del lenguaje C#.
Declare una unión cuando un valor debe ser exactamente uno de un conjunto fijo de tipos y desea que el compilador aplique que se controle cada posibilidad. Entre los escenarios habituales se incluyen los siguientes:
-
Resultado o error devuelve: un método devuelve un valor correcto o un valor de error, y el autor de la llamada debe controlar ambos. Una unión como
union Result(Success, Error)hace que el conjunto de resultados sea explícito. -
Envío de mensajes o comandos: un sistema procesa un conjunto cerrado de tipos de mensajes. Una unión garantiza que los nuevos tipos de mensajes generen advertencias en tiempo de compilación en cada
switchque aún no las controlen. - Reemplazar interfaces de marcador o clases base abstractas: si usa una interfaz o una clase abstracta únicamente para agrupar tipos para la coincidencia de patrones, una unión le proporciona una comprobación exhaustiva sin necesidad de herencia o miembros compartidos.
Una unión difiere de otras declaraciones de tipo de maneras importantes:
- A diferencia de o
classstruct, una unión no define nuevos miembros de datos. En su lugar, compone los tipos existentes en un conjunto cerrado de alternativas. -
interfaceA diferencia de , se cierra una unión: se define la lista completa de tipos de mayúsculas y minúsculas de la declaración y el compilador usa esa lista para las comprobaciones de exhaustivas. - A diferencia de ,
recorduna unión no agrega igualdad, clonación ni comportamiento de deconstrucción. Una unión se centra en "qué caso es así?" en lugar de "qué campos tiene?"
Declaraciones de unión
Una declaración de unión especifica un nombre y una lista de tipos de mayúsculas y minúsculas:
public union Pet(Cat, Dog, Bird);
Los tipos de caso pueden ser cualquier tipo que se convierta en object, incluidas clases, estructuras, interfaces, parámetros de tipo, tipos que aceptan valores NULL y otras uniones. En los ejemplos siguientes se muestran diferentes posibilidades de tipo de caso:
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);
Cuando un tipo de caso es un tipo de valor (como int), el valor se conversión boxing cuando se almacena en la propiedad de Value la unión. Las uniones almacenan su contenido como una sola object? referencia.
Una declaración de unión puede incluir un cuerpo con miembros adicionales, al igual que una estructura, sujeto a algunas restricciones. Las declaraciones de unión no pueden incluir campos de instancia, propiedades automáticas ni eventos similares a campos. Tampoco puede declarar constructores públicos con un único parámetro, ya que el compilador genera esos constructores como miembros de creación de unión. La unión siguiente Length agrega una propiedad que usa la TotalMeters coincidencia de patrones para controlar cada tipo de caso, junto con un Add método que combina dos longitudes:
public record class Meters(double Value);
public record class Feet(double Value);
public union Length(Meters, Feet)
{
public double TotalMeters => this switch
{
Meters m => m.Value,
Feet f => f.Value * 0.3048,
_ => throw new InvalidOperationException("The Length has no value."),
};
public Length Add(Length other) => new Meters(TotalMeters + other.TotalMeters);
}
Conversiones de unión
Existe una conversión de unión implícita de cada tipo de caso al tipo de unión:
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 }
}
Las conversiones de unión funcionan llamando al constructor generado correspondiente. Si existe un operador de conversión implícita definido por el usuario para el mismo tipo, el operador definido por el usuario tiene prioridad sobre la conversión de unión. Si más de un tipo de caso es igualmente aplicable al valor de origen, la conversión de unión es ambigua y el compilador notifica un error. Para más información sobre la prioridad de conversión, consulte la especificación de características.
Una conversión de unión a una estructura de unión que acepta valores NULL (T?) también funciona cuando T es un tipo de unión:
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",
};
}
Coincidencia de patrones de unión
Cuando el patrón coincide con un tipo de unión, los patrones se aplican generalmente a la propiedad de Value la unión, no al propio valor de unión. Este comportamiento de "desencapsulado" significa que la unión es transparente para la coincidencia de patrones:
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
}
Tres patrones son excepciones a esta regla: el patrón de descarte _ , el var patrón y el not patrón se aplican al propio valor de unión, no a su Value propiedad. Use var para capturar el valor de unión cuando GetPet() devuelve (Pet?Nullable<Pet>):
if (GetPet() is var pet) { /* pet is the Pet? value returned from GetPet */ }
En los patrones lógicos, cada rama sigue la regla de desencapsulado individualmente. La rama izquierda de un and patrón puede cambiar el valor entrante que ve la rama derecha. Dado que el not patrón se aplica al valor de unión entrante en lugar de a su Value, un inicial not null no desencapsula el valor de la rama que le sigue:
GetPet() switch
{
// 'var pet' captures the Pet?; 'not null' applies to the Pet? value (not pet.Value)
var pet and not null => ...,
// 'not null' doesn't unwrap to Pet, so 'var value' still captures the Pet?
not null and var value => ...,
}
Nota:
Dado que los patrones se aplican a Value, un patrón como pet is Pet normalmente no coincide, ya que Pet se prueba con el contenido de la unión, no con la propia unión.
Coincidencia nula
En el caso de las uniones de estructura, el null patrón comprueba si Value es 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
}
En el caso de las uniones basadas en clases, null se realiza correctamente cuando la propia referencia de unión es null o su Value propiedad es 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 */ }
Para los tipos de estructura de unión que aceptan valores NULL (Pet?), null se ejecuta correctamente cuando el contenedor que acepta valores NULL no tiene ningún valor o cuando el valor de Value la unión subyacente es NULL.
Exhaustividad de la unión
Una switch expresión es exhaustiva cuando controla todos los tipos de casos de una unión. El compilador advierte solo si no se controla un tipo de caso. No es necesario incluir un patrón de descarte (_) o var un patrón para que coincida con cualquier tipo cuando se asigne definitivamente la expresión:
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
}
Si el estado NULL de la propiedad de Value la unión es "tal vez null", también debe controlar null para evitar una advertencia:
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
}
Esta situación puede surgir cuando la union expresión es el valor predeterminado o no se asigna definitivamente, como se muestra en el ejemplo anterior.
Nulabilidad
El compilador realiza un seguimiento del estado NULL de la propiedad de Value una unión mediante las reglas siguientes:
- El estado null predeterminado de la propiedad de
Valueuna unión es "tal vez null" si el estado null predeterminado de cualquier tipo de caso es "tal vez null". De lo contrario, el estado null predeterminado es "not null". - Cuando se crea un valor de unión a partir de un tipo de caso (a través de un constructor o conversión de unión),
Valueobtiene el estado NULL del valor entrante. - Cuando los miembros o
HasValuedelTryGetValue(...)patrón de acceso que no son boxing consultan el contenido de la unión, el estado null deValuese convierte en "no null" en latruerama.
Tipos de unión personalizados
El compilador convierte una union declaración en una struct declaración. La estructura se marca con el [System.Runtime.CompilerServices.Union] atributo e implementa la IUnion interfaz . Incluye un constructor público y una conversión implícita para cada tipo de caso junto con una Value propiedad . Ese formulario generado se opina. Siempre es un struct, siempre se escriben los casos de tipo de valor y siempre se almacena el contenido como object?.
Es posible que necesite un comportamiento diferente si desea adaptar un tipo existente, crear una unión basada en clases o usar una estrategia de almacenamiento personalizada o si necesita compatibilidad con la interoperabilidad. Puede crear manualmente un tipo de unión.
Cualquier clase o estructura con un [Union] atributo es un tipo de unión si sigue el patrón de unión básico. El patrón de unión básico requiere:
- Atributo
[Union]del tipo. - Uno o varios constructores públicos, cada uno con un solo valor o
inparámetro. El tipo de parámetro de cada constructor define un tipo de caso. - Propiedad pública
Valuede tipoobject?(oobject) con ungetdescriptor de acceso.
Todos los miembros de la unión anteriores deben ser públicos. El compilador usa estos miembros para implementar conversiones de unión, coincidencia de patrones y comprobaciones de exhaustividad. También puede implementar el patrón de acceso no boxing o crear un tipo de unión basado en clases. El tipo de unión personalizada puede agregar miembros adicionales.
El compilador supone que los tipos de unión personalizados cumplen estas reglas de comportamiento:
-
Sonido:
Valuesiempre devuelvenullo un valor de uno de los tipos de caso: nunca un valor de un tipo diferente. Para uniones de estructura,defaultgenera unValuedenull. -
Estabilidad: si crea un valor de unión a partir de un tipo de caso,
Valuecoincide con ese tipo de caso (o esnullsi la entrada eranull). - Equivalencia de creación: si un valor se puede convertir implícitamente en dos tipos de casos diferentes, ambos miembros de creación producen el mismo comportamiento observable.
-
Coherencia del patrón de acceso: los
HasValuemiembros yTryGetValue, si están presentes, se comportan de forma equivalente a comprobarValuedirectamente.
En el ejemplo siguiente se muestra un tipo de unión personalizado:
[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
}
Patrón de acceso sin conversión boxing
Un tipo de unión personalizado puede implementar opcionalmente el patrón de acceso no boxing para permitir el acceso fuertemente tipado a casos de tipo valor sin conversión boxing durante la coincidencia de patrones. Este patrón requiere:
- Propiedad
HasValuede tipoboolque devuelvetruecuandoValuenonulles . - Método
TryGetValuepara cada tipo de caso que devuelvebooly entrega el valor a través de unoutparámetro .TryGetValuedevuelvetruesolo cuandoValuees un valor distinto de NULL de ese tipo de caso. Elouttipo del parámetro es identity-convertible al tipo de caso o al tipo de valor subyacente cuando el tipo de caso es un tipo de valor que acepta valores NULL.
[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
}
El compilador prefiere TryGetValue sobre la propiedad al implementar la Value coincidencia de patrones, lo que evita los tipos de valor boxing.
Proveedores de miembros de unión
Un tipo de unión puede delegar sus miembros de unión en una interfaz anidada IUnionMembers . Cuando esta interfaz está presente, el compilador busca Create métodos de fábrica en lugar de constructores:
[System.Runtime.CompilerServices.Union]
public record class Outcome<T> : Outcome<T>.IUnionMembers
{
private readonly object? _value;
private Outcome(object? value) => _value = value;
public interface IUnionMembers
{
static Outcome<T> Create(T? value) => new(value);
static Outcome<T> Create(Exception? value) => new(value);
object? Value { get; }
}
object? IUnionMembers.Value => _value;
}
Los proveedores de miembros de unión son útiles cuando el tipo de unión necesita un constructor privado o cuando la lógica de creación requiere un patrón de fábrica, como con record class tipos de unión.
Tipos de unión basados en clases
Una clase también puede ser un tipo de unión. Este tipo de unión es útil cuando se necesita la semántica de referencia o la herencia:
[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",
};
}
En el caso de las uniones basadas en clases, el null patrón coincide con una referencia nula y un valor NULL Value.
Implementación de unión
Los tipos de unión se basan en los UnionAttribute tipos y IUnion en el System.Runtime.CompilerServices espacio de nombres . El entorno de ejecución incluye estos tipos a partir de .NET 11 Versión preliminar 5:
namespace System.Runtime.CompilerServices;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false)]
public sealed class UnionAttribute : Attribute;
public interface IUnion
{
object? Value { get; }
}
Las declaraciones de unión generadas por el compilador implementan IUnion. Puede comprobar si hay cualquier valor de unión en tiempo de ejecución mediante IUnion:
if (value is IUnion { Value: null }) { /* the union's value is null */ }
Al declarar un union tipo, el compilador genera una estructura que implementa IUnion. Por ejemplo, la Pet declaración (public union Pet(Cat, Dog, Bird);) equivale a:
[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; }
}
Especificación del lenguaje C#
Para obtener más información, consulte la especificación de características Uniones .