Luego de mucho trabajar hemos encontrado el resultado de este rompecabezas que algunos los lectores de este espacio tienen. Si tienes algo más que aportar no dejes de compartir tu conocimiento.
Solución:
Realmente no me gustan las soluciones de verificación de tipos y conversión de tipos proporcionadas anteriormente, así que aquí hay una unión 100% segura de tipos que arrojará errores de compilación si intenta usar el tipo de datos incorrecto:
using System;
namespace Juliet
class Program
static void Main(string[] args)
Union3[] unions = new Union3[]
new Union3.Case1(5),
new Union3.Case2('x'),
new Union3.Case3("Juliet")
;
foreach (Union3 union in unions)
string value = union.Match(
num => num.ToString(),
character => new string(new char[] character ),
word => word);
Console.WriteLine("Matched union with value '0'", value);
Console.ReadLine();
public abstract class Union3
public abstract T Match(Func f, Func g, Func h);
// private ctor ensures no external classes can inherit
private Union3()
public sealed class Case1 : Union3
public readonly A Item;
public Case1(A item) : base() this.Item = item;
public override T Match(Func f, Func g, Func h)
return f(Item);
public sealed class Case2 : Union3
public readonly B Item;
public Case2(B item) this.Item = item;
public override T Match(Func f, Func g, Func h)
return g(Item);
public sealed class Case3 : Union3
public readonly C Item;
public Case3(C item) this.Item = item;
public override T Match(Func f, Func g, Func h)
return h(Item);
Me gusta la dirección de la solución aceptada, pero no se adapta bien a las uniones de más de tres elementos (por ejemplo, una unión de 9 elementos requeriría 9 definiciones de clase).
Aquí hay otro enfoque que también es 100% seguro para los tipos en tiempo de compilación, pero que es fácil de ampliar a uniones grandes.
public class UnionBase
dynamic value;
public UnionBase(A a) value = a;
protected UnionBase(object x) value = x;
protected T InternalMatch(params Delegate[] ds)
var vt = value.GetType();
foreach (var d in ds)
var mi = d.Method;
// These are always true if InternalMatch is used correctly.
Debug.Assert(mi.GetParameters().Length == 1);
Debug.Assert(typeof(T).IsAssignableFrom(mi.ReturnType));
var pt = mi.GetParameters()[0].ParameterType;
if (pt.IsAssignableFrom(vt))
return (T)mi.Invoke(null, new object[] value );
throw new Exception("No appropriate matching function was provided");
public T Match(Func fa) return InternalMatch(fa);
public class Union : UnionBase
public Union(A a) : base(a)
public Union(B b) : base(b)
protected Union(object x) : base(x)
public T Match(Func fa, Func fb) return InternalMatch(fa, fb);
public class Union : Union
public Union(A a) : base(a)
public Union(B b) : base(b)
public Union(C c) : base(c)
protected Union(object x) : base(x)
public T Match(Func fa, Func fb, Func fc) return InternalMatch(fa, fb, fc);
public class Union : Union
public Union(A a) : base(a)
public Union(B b) : base(b)
public Union(C c) : base(c)
public Union(D d) : base(d)
protected Union(object x) : base(x)
public T Match(Func fa, Func fb, Func fc, Func fd) return InternalMatch(fa, fb, fc, fd);
public class Union : Union
public Union(A a) : base(a)
public Union(B b) : base(b)
public Union(C c) : base(c)
public Union(D d) : base(d)
public Union(E e) : base(e)
protected Union(object x) : base(x)
public T Match(Func fa, Func fb, Func fc, Func fd, Func fe) return InternalMatch(fa, fb, fc, fd, fe);
public class DiscriminatedUnionTest : IExample
public Union MakeUnion(int n)
return new Union(n);
public Union MakeUnion(bool b)
return new Union(b);
public Union MakeUnion(string s)
return new Union(s);
public Union MakeUnion(params int[] xs)
return new Union(xs);
public void Print(Union union)
var text = union.Match(
n => "This is an int " + n.ToString(),
b => "This is a boolean " + b.ToString(),
s => "This is a string" + s,
xs => "This is an array of ints " + String.Join(", ", xs));
Console.WriteLine(text);
public void Run()
Print(MakeUnion(1));
Print(MakeUnion(true));
Print(MakeUnion("forty-two"));
Print(MakeUnion(0, 1, 1, 2, 3, 5, 8));
Escribí algunas publicaciones de blog sobre este tema que podrían ser útiles:
- Tipos de unión en C #
- Implementación de Tic-Tac-Toe usando clases estatales
Supongamos que tiene un escenario de carrito de la compra con tres estados: “Vacío”, “Activo” y “Pagado”, cada uno con diferente comportamiento.
- Tu creas tener un
ICartState
interfaz que todos los estados tienen en común (y podría ser simplemente una interfaz de marcador vacía) - Creas tres clases que implementan esa interfaz. (Las clases no tienen que estar en una relación de herencia)
- La interfaz contiene un método de “plegado”, mediante el cual pasa una lambda para cada estado o caso que necesita manejar.
Puede usar el tiempo de ejecución de F # de C #, pero como una alternativa más liviana, he escrito una pequeña plantilla T4 para generar código como este.
Aquí está la interfaz:
partial interface ICartState
ICartState Transition(
Func cartStateEmpty,
Func cartStateActive,
Func cartStatePaid
);
Y aquí está la implementación:
class CartStateEmpty : ICartState
ICartState ICartState.Transition(
Func cartStateEmpty,
Func cartStateActive,
Func cartStatePaid
)
// I'm the empty state, so invoke cartStateEmpty
return cartStateEmpty(this);
class CartStateActive : ICartState
ICartState ICartState.Transition(
Func cartStateEmpty,
Func cartStateActive,
Func cartStatePaid
)
// I'm the active state, so invoke cartStateActive
return cartStateActive(this);
class CartStatePaid : ICartState
ICartState ICartState.Transition(
Func cartStateEmpty,
Func cartStateActive,
Func cartStatePaid
)
// I'm the paid state, so invoke cartStatePaid
return cartStatePaid(this);
Ahora digamos que extiendes el CartStateEmpty
y CartStateActive
con un AddItem
método que es no Implementado por CartStatePaid
.
Y también digamos que CartStateActive
tiene un Pay
método que los otros estados no tienen.
Luego, aquí hay un código que lo muestra en uso: agregar dos artículos y luego pagar el carrito:
public ICartState AddProduct(ICartState currentState, Product product)
return currentState.Transition(
cartStateEmpty => cartStateEmpty.AddItem(product),
cartStateActive => cartStateActive.AddItem(product),
cartStatePaid => cartStatePaid // not allowed in this case
);
public void Example()
var currentState = new CartStateEmpty() as ICartState;
//add some products
currentState = AddProduct(currentState, Product.ProductX);
currentState = AddProduct(currentState, Product.ProductY);
//pay
const decimal paidAmount = 12.34m;
currentState = currentState.Transition(
cartStateEmpty => cartStateEmpty, // not allowed in this case
cartStateActive => cartStateActive.Pay(paidAmount),
cartStatePaid => cartStatePaid // not allowed in this case
);
Tenga en cuenta que este código es completamente seguro para tipos: no hay conversiones ni condicionales en ninguna parte, y errores de compilación si intenta pagar por un carrito vacío, por ejemplo.
Si te sientes estimulado, tienes la habilidad dejar una crónica acerca de qué le añadirías a este enunciado.