Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los 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?"
Importante
En .NET 11 Preview 2, el entorno de ejecución no incluye la UnionAttribute interfaz y IUnion . Para usar tipos de unión, debe declararlos usted mismo. Para ver las declaraciones necesarias, consulte Implementación de la Unión.
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 uniones:
public union OneOrMore<T>(T, IEnumerable<T>)
{
public IEnumerable<T> AsEnumerable() => Value switch
{
T single => [single],
IEnumerable<T> multiple => multiple,
_ => []
};
}
Conversiones de unión
Existe una conversión de unión implícita de cada tipo de caso al tipo de unión. No es necesario llamar explícitamente a un constructor:
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. Para más información sobre la prioridad de conversión, consulte la especificación del lenguaje.
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 uniones
Cuando el patrón coincide con un tipo de unión, los patrones se aplican 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
}
Dos patrones son excepciones a esta regla: el var patrón y el patrón de descarte _ 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 (Nullable<Pet>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. El patrón siguiente comprueba que no Pet? es NULL y que Value no es NULL:
GetPet() switch
{
var pet and not null => ..., // 'var pet' captures the Pet?; 'not null' checks 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:
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
}
Nulabilidad
El compilador realiza un seguimiento del estado NULL de la propiedad de Value una unión mediante las reglas siguientes:
- 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
TryGetValue(...)delHasValuepatró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. El struct se marca con el [System.Runtime.CompilerServices.Union] atributo , 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?.
Cuando se necesita un comportamiento diferente, como una unión basada en clases, una estrategia de almacenamiento personalizada, compatibilidad con la interoperabilidad o si desea adaptar un tipo existente, 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 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 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 .
[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.
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
El atributo y la interfaz siguientes admiten tipos de unión en tiempo de compilación y tiempo de ejecución:
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; }
}
Importante
En .NET 11 Preview 2, estos tipos no se incluyen en el entorno de ejecución. Para usar tipos de unión, debe declararlos en el proyecto. Se incluirán en una versión preliminar futura de .NET.
Especificación del lenguaje C#
Para obtener más información, consulte la especificación de características Uniones .