[Nota: Esta pregunta tenía el título original " Unión de estilo C (ish) en C # ", pero como me informó el comentario de Jeff, aparentemente esta estructura se llama 'unión discriminada']
Disculpe la verbosidad de esta pregunta.
Hay un par de preguntas que suenan similares a las mías ya en SO, pero parecen concentrarse en los beneficios de ahorro de memoria de la unión o en su uso para la interoperabilidad. Aquí hay un ejemplo de tal pregunta .
Mi deseo de tener algo tipo sindicato es algo diferente.
Estoy escribiendo un código en este momento que genera objetos que se parecen un poco a esto
public class ValueWrapper
{
public DateTime ValueCreationDate;
// ... other meta data about the value
public object ValueA;
public object ValueB;
}
Cosas bastante complicadas, creo que estarás de acuerdo. El caso es que ValueA
solo puede ser de algunos tipos determinados (digamos string
, int
y Foo
(que es una clase) y ValueB
puede ser otro pequeño conjunto de tipos. No me gusta tratar estos valores como objetos (quiero la sensación cálida y acogedora de codificación con un poco de seguridad de tipo).
Así que pensé en escribir una pequeña clase contenedora trivial para expresar el hecho de que ValueA lógicamente es una referencia a un tipo en particular. Llamé a la clase Union
porque lo que estoy tratando de lograr me recordó el concepto de unión en C.
public class Union<A, B, C>
{
private readonly Type type;
public readonly A a;
public readonly B b;
public readonly C c;
public A A{get {return a;}}
public B B{get {return b;}}
public C C{get {return c;}}
public Union(A a)
{
type = typeof(A);
this.a = a;
}
public Union(B b)
{
type = typeof(B);
this.b = b;
}
public Union(C c)
{
type = typeof(C);
this.c = c;
}
/// <summary>
/// Returns true if the union contains a value of type T
/// </summary>
/// <remarks>The type of T must exactly match the type</remarks>
public bool Is<T>()
{
return typeof(T) == type;
}
/// <summary>
/// Returns the union value cast to the given type.
/// </summary>
/// <remarks>If the type of T does not exactly match either X or Y, then the value <c>default(T)</c> is returned.</remarks>
public T As<T>()
{
if(Is<A>())
{
return (T)(object)a; // Is this boxing and unboxing unavoidable if I want the union to hold value types and reference types?
//return (T)x; // This will not compile: Error = "Cannot cast expression of type 'X' to 'T'."
}
if(Is<B>())
{
return (T)(object)b;
}
if(Is<C>())
{
return (T)(object)c;
}
return default(T);
}
}
El uso de esta clase ValueWrapper ahora se ve así
public class ValueWrapper2
{
public DateTime ValueCreationDate;
public Union<int, string, Foo> ValueA;
public Union<double, Bar, Foo> ValueB;
}
que es algo así como lo que quería lograr, pero me falta un elemento bastante crucial: la verificación de tipos impuesta por el compilador al llamar a las funciones Is y As, como lo demuestra el siguiente código
public void DoSomething()
{
if(ValueA.Is<string>())
{
var s = ValueA.As<string>();
// .... do somethng
}
if(ValueA.Is<char>()) // I would really like this to be a compile error
{
char c = ValueA.As<char>();
}
}
En mi opinión, no es válido preguntar a ValueA si es un, char
ya que su definición dice claramente que no lo es; esto es un error de programación y me gustaría que el compilador lo captara . [Además, si pudiera hacer esto correctamente, entonces (con suerte) obtendría intellisense también, lo cual sería una bendición].
Para lograr esto, me gustaría decirle al compilador que el tipo T
puede ser uno de A, B o C
public bool Is<T>() where T : A
or T : B // Yes I know this is not legal!
or T : C
{
return typeof(T) == type;
}
¿Alguien tiene alguna idea de si lo que quiero lograr es posible? ¿O simplemente soy estúpido por escribir esta clase en primer lugar?
Gracias por adelantado.
StructLayout(LayoutKind.Explicit)
yFieldOffset
. Esto no se puede hacer con tipos de referencia, por supuesto. Lo que está haciendo no es como una Unión C en absoluto.