Pregunta

[Nota: Esta pregunta tenía el título original " C (más o menos) la unión de estilo en C # " pero a medida que el comentario de Jeff me informó, al parecer, esta estructura se denomina 'unión discriminada']

Con permiso el nivel de detalle de esta cuestión.

Hay un par de preguntas que suenan similares a las minas que ya están en SO pero parecen concentrarse en salvar la memoria beneficios de la unión o de utilizarlo para la interoperabilidad. Aquí es un ejemplo de una pregunta tan .

Mi deseo de tener una cosa de tipo de unión es algo diferente.

Estoy escribiendo algo de código en el momento que genera objetos que se ven un poco como este

public class ValueWrapper
{
    public DateTime ValueCreationDate;
    // ... other meta data about the value

    public object ValueA;
    public object ValueB;
}

Cosas bastante complicado creo que estarán de acuerdo. El caso es que ValueA sólo puede ser de unos determinados tipos (digamos string, int y Foo (que es una clase) y ValueB pueden ser otro pequeño conjunto de tipos. No me gusta el tratamiento de estos valores como objetos (quiero la cálida perfectamente sensación de codificación con un poco de seguridad de tipos).

Así que pensé en escribir una pequeña clase de contenedor trivial para expresar el hecho de que ValorA lógicamente es una referencia a un tipo particular. Llamé al Union clase porque lo que estoy tratando de lograr me recordó el concepto 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 importante - que es el compilador hace cumplir la comprobación de tipos cuando se llama al CUAL y SEGÚN funciones que el código siguiente se muestra

    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>();
        }
    }

OMI No es válido preguntarse ValorA si es un char desde su definición dice claramente que no es - se trata de un error de programación, y me gustaría que el compilador darán cuenta de esto. [También, si pudiera conseguir esta correcta, entonces (esperemos) Me gustaría obtener IntelliSense también -. Lo que sería una gran ayuda]

Para lograr esto querría decir 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 estoy simplemente estúpido para escribir esta clase en el primer lugar?

Gracias de antemano.

¿Fue útil?

Solución

Yo no me gusta mucho el tipo de comprobación y soluciones de tipo de fundición a presión proporcionada anteriormente, así que aquí está 100% con seguridad de la unión que se generan errores de compilación si se intenta utilizar el tipo de datos incorrecto:

using System;

namespace Juliet
{
    class Program
    {
        static void Main(string[] args)
        {
            Union3<int, char, string>[] unions = new Union3<int,char,string>[]
                {
                    new Union3<int, char, string>.Case1(5),
                    new Union3<int, char, string>.Case2('x'),
                    new Union3<int, char, string>.Case3("Juliet")
                };

            foreach (Union3<int, char, string> 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<A, B, C>
    {
        public abstract T Match<T>(Func<A, T> f, Func<B, T> g, Func<C, T> h);
        // private ctor ensures no external classes can inherit
        private Union3() { } 

        public sealed class Case1 : Union3<A, B, C>
        {
            public readonly A Item;
            public Case1(A item) : base() { this.Item = item; }
            public override T Match<T>(Func<A, T> f, Func<B, T> g, Func<C, T> h)
            {
                return f(Item);
            }
        }

        public sealed class Case2 : Union3<A, B, C>
        {
            public readonly B Item;
            public Case2(B item) { this.Item = item; }
            public override T Match<T>(Func<A, T> f, Func<B, T> g, Func<C, T> h)
            {
                return g(Item);
            }
        }

        public sealed class Case3 : Union3<A, B, C>
        {
            public readonly C Item;
            public Case3(C item) { this.Item = item; }
            public override T Match<T>(Func<A, T> f, Func<B, T> g, Func<C, T> h)
            {
                return h(Item);
            }
        }
    }
}

Otros consejos

Me gusta la dirección de la solución aceptada pero no se adapta bien a los sindicatos de más de tres elementos (por ejemplo una unión de 9 artículos requeriría 9 definiciones de clases).

Aquí hay otro enfoque que es también el 100% de seguridad de tipos en tiempo de compilación, pero que es fácil de cultivar a los grandes sindicatos.

public class UnionBase<A>
{
    dynamic value;

    public UnionBase(A a) { value = a; } 
    protected UnionBase(object x) { value = x; }

    protected T InternalMatch<T>(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<T>(Func<A, T> fa) { return InternalMatch<T>(fa); }
}

public class Union<A, B> : UnionBase<A>
{
    public Union(A a) : base(a) { }
    public Union(B b) : base(b) { }
    protected Union(object x) : base(x) { }
    public T Match<T>(Func<A, T> fa, Func<B, T> fb) { return InternalMatch<T>(fa, fb); }
}

public class Union<A, B, C> : Union<A, B>
{
    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<T>(Func<A, T> fa, Func<B, T> fb, Func<C, T> fc) { return InternalMatch<T>(fa, fb, fc); }
}

public class Union<A, B, C, D> : Union<A, B, C>
{
    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<T>(Func<A, T> fa, Func<B, T> fb, Func<C, T> fc, Func<D, T> fd) { return InternalMatch<T>(fa, fb, fc, fd); }
}

public class Union<A, B, C, D, E> : Union<A, B, C, D>
{
    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<T>(Func<A, T> fa, Func<B, T> fb, Func<C, T> fc, Func<D, T> fd, Func<E, T> fe) { return InternalMatch<T>(fa, fb, fc, fd, fe); }
}

public class DiscriminatedUnionTest : IExample
{
    public Union<int, bool, string, int[]> MakeUnion(int n)
    {
        return new Union<int, bool, string, int[]>(n);
    }

    public Union<int, bool, string, int[]> MakeUnion(bool b)
    {
        return new Union<int, bool, string, int[]>(b);
    }

    public Union<int, bool, string, int[]> MakeUnion(string s)
    {
        return new Union<int, bool, string, int[]>(s);
    }

    public Union<int, bool, string, int[]> MakeUnion(params int[] xs)
    {
        return new Union<int, bool, string, int[]>(xs);
    }

    public void Print(Union<int, bool, string, int[]> 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));
    }
}

Me escribió algunas entradas de blog sobre este tema que podría ser útil:

ejemplo de le permiten tener un escenario carro de compras con tres estados:. "Vacío", "activa" y "pago", cada uno con diferente Comportamiento

  • crear una interfaz ICartState que todos los estados tienen en común (y que sólo podrían ser una interfaz de marcador de vacío)
  • Se crea tres clases que implementan la interfaz. (Las clases no tienen que estar en una relación de herencia)
  • La interfaz contiene un "veces" método, mediante el cual se pasa un lambda en para cada estado o caso de que necesite de manejar.

Se puede usar el F # tiempo de ejecución de C #, sino como una alternativa más ligera de peso, he escrito una plantilla T4 poco para la generación de código como este.

Esta es la unión:

partial interface ICartState
{
  ICartState Transition(
        Func<CartStateEmpty, ICartState> cartStateEmpty,
        Func<CartStateActive, ICartState> cartStateActive,
        Func<CartStatePaid, ICartState> cartStatePaid
        );
}

Y aquí está la aplicación:

class CartStateEmpty : ICartState
{
  ICartState ICartState.Transition(
        Func<CartStateEmpty, ICartState> cartStateEmpty,
        Func<CartStateActive, ICartState> cartStateActive,
        Func<CartStatePaid, ICartState> cartStatePaid
        )
  {
        // I'm the empty state, so invoke cartStateEmpty 
      return cartStateEmpty(this);
  }
}

class CartStateActive : ICartState
{
  ICartState ICartState.Transition(
        Func<CartStateEmpty, ICartState> cartStateEmpty,
        Func<CartStateActive, ICartState> cartStateActive,
        Func<CartStatePaid, ICartState> cartStatePaid
        )
  {
        // I'm the active state, so invoke cartStateActive
      return cartStateActive(this);
  }
}

class CartStatePaid : ICartState
{
  ICartState ICartState.Transition(
        Func<CartStateEmpty, ICartState> cartStateEmpty,
        Func<CartStateActive, ICartState> cartStateActive,
        Func<CartStatePaid, ICartState> cartStatePaid
        )
  {
        // I'm the paid state, so invoke cartStatePaid
      return cartStatePaid(this);
  }
}

Ahora digamos que se extiende el CartStateEmpty y CartStateActive con un método AddItem que es no implementado por CartStatePaid.

Y también digamos que tiene un método CartStateActive Pay que los otros estados no tienen.

Entonces aquí hay un código que lo muestra en su uso - Adición de dos artículos y luego pagan por el carro:

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 typesafe -. Sin colada o condicionales en cualquier lugar, y los errores de compilación si se trata de pagar por un carro vacío, digamos

He escrito una biblioteca para hacer esto en https://github.com/mcintyre321/OneOf

  

Instalar Paquete Oneof

Tiene los tipos genéricos en él para hacer los UI por ejemplo, OneOf<T0, T1> todo el camino hasta OneOf<T0, ..., T9>. Cada uno de ellos tiene un .Match, y una declaración .Switch que se puede utilizar para el compilador de comportamiento con tipo seguro, por ejemplo:.

`` `

OneOf<string, ColorName, Color> backgroundColor = getBackground(); 
Color c = backgroundColor.Match(
    str => CssHelper.GetColorFromString(str),
    name => new Color(name),
    col => col
);

`` `

No estoy seguro de entender plenamente su objetivo. En C, una unión es una estructura que utiliza las mismas posiciones de memoria para más de un campo. Por ejemplo:

typedef union
{
    float real;
    int scalar;
} floatOrScalar;

La unión floatOrScalar podría ser utilizado como un flotador o un int, pero ambos consumen el mismo espacio de memoria. El cambio de uno cambia el otro. Usted puede lograr lo mismo con una estructura en C #:

[StructLayout(LayoutKind.Explicit)]
struct FloatOrScalar
{
    [FieldOffset(0)]
    public float Real;
    [FieldOffset(0)]
    public int Scalar;
}

Los anteriores usos estructura 32bits total de, en lugar de 64bits. Esto sólo es posible con una estructura. Su ejemplo anterior es una clase, y dada la naturaleza del CLR, no ofrece ninguna garantía sobre la eficacia de la memoria. Si cambia un Union<A, B, C> de un tipo a otro, que no son necesariamente vuelve a utilizar la memoria ... lo más probable, se están asignando un nuevo tipo en el montón y colocar un puntero diferente en el campo respaldo object. Contrariamente a un unión real , el enfoque puede llegar a causar más golear montón de lo que se conseguiría de otra manera, si no se ha utilizado el tipo de Unión.

char foo = 'B';

bool bar = foo is int;

El resultado es una advertencia, no un error. Si usted está buscando para sus funciones y Is As a ser análogos a los operadores de C #, entonces no deberías restringirlos de esa manera de todos modos.

Si usted permite que múltiples tipos, no se puede lograr la seguridad de tipos (a no ser que los tipos están relacionados).

no puede y no lograr ningún tipo de seguridad de tipos, sólo se podía lograr valor de byte-seguridad usando FieldOffset.

No tendría mucho más sentido tener un ValueWrapper<T1, T2> genérico con T1 ValueA y T2 ValueB, ...

P.S .: cuando se habla de seguridad de tipos me refiero tiempo de compilación tipo de seguridad.

Si necesita un envoltorio de código (la realización de la lógica bussiness en las modificaciones que se puede usar algo en la línea de:

public class Wrapper
{
    public ValueHolder<int> v1 = 5;
    public ValueHolder<byte> v2 = 8;
}

public struct ValueHolder<T>
    where T : struct
{
    private T value;

    public ValueHolder(T value) { this.value = value; }

    public static implicit operator T(ValueHolder<T> valueHolder) { return valueHolder.value; }
    public static implicit operator ValueHolder<T>(T value) { return new ValueHolder<T>(value); }
}

Para un camino más fácil que usted podría utilizar (tiene problemas de rendimiento, pero es muy simple):

public class Wrapper
{
    private object v1;
    private object v2;

    public T GetValue1<T>() { if (v1.GetType() != typeof(T)) throw new InvalidCastException(); return (T)v1; }
    public void SetValue1<T>(T value) { v1 = value; }

    public T GetValue2<T>() { if (v2.GetType() != typeof(T)) throw new InvalidCastException(); return (T)v2; }
    public void SetValue2<T>(T value) { v2 = value; }
}

//usage:
Wrapper wrapper = new Wrapper();
wrapper.SetValue1("aaaa");
wrapper.SetValue2(456);

string s = wrapper.GetValue1<string>();
DateTime dt = wrapper.GetValue1<DateTime>();//InvalidCastException

Aquí está mi intento. Lo hace tiempo de compilación de tipos, mediante restricciones de tipo genérico.

class Union {
    public interface AllowedType<T> { };

    internal object val;

    internal System.Type type;
}

static class UnionEx {
    public static T As<U,T>(this U x) where U : Union, Union.AllowedType<T> {
        return x.type == typeof(T) ?(T)x.val : default(T);
    }

    public static void Set<U,T>(this U x, T newval) where U : Union, Union.AllowedType<T> {
        x.val = newval;
        x.type = typeof(T);
    }

    public static bool Is<U,T>(this U x) where U : Union, Union.AllowedType<T> {
        return x.type == typeof(T);
    }
}

class MyType : Union, Union.AllowedType<int>, Union.AllowedType<string> {}

class TestIt
{
    static void Main()
    {
        MyType bla = new MyType();
        bla.Set(234);
        System.Console.WriteLine(bla.As<MyType,int>());
        System.Console.WriteLine(bla.Is<MyType,string>());
        System.Console.WriteLine(bla.Is<MyType,int>());

        bla.Set("test");
        System.Console.WriteLine(bla.As<MyType,string>());
        System.Console.WriteLine(bla.Is<MyType,string>());
        System.Console.WriteLine(bla.Is<MyType,int>());

        // compile time errors!
        // bla.Set('a'); 
        // bla.Is<MyType,char>()
    }
}

Se podría utilizar un poco de embellecer arriba. Sobre todo, no podía encontrar la manera de deshacerse de los parámetros de tipo AS / IS / Set (no hay una forma de especificar un parámetro de tipo y sea C # figura el otro?)

Así que he dado en este mismo problema muchas veces, y me acaba de llegar a una solución que obtiene la sintaxis que quiero (a expensas de alguna fealdad en la aplicación del tipo Unión.)

Para recapitular: queremos que este tipo de uso en el lugar de la llamada

.
Union<int, string> u;

u = 1492;
int yearColumbusDiscoveredAmerica = u;

u = "hello world";
string traditionalGreeting = u;

var answers = new SortedList<string, Union<int, string, DateTime>>();
answers["life, the universe, and everything"] = 42;
answers["D-Day"] = new DateTime(1944, 6, 6);
answers["C#"] = "is awesome";

Queremos que los siguientes ejemplos que no pueden compilar, sin embargo, para que podamos obtener un mínimo de seguridad de tipos.

DateTime dateTimeColumbusDiscoveredAmerica = u;
Foo fooInstance = u;

Para el crédito adicional, también vamos a no ocupa más espacio que el estrictamente necesario.

Con todo lo dicho, aquí está mi aplicación de dos parámetros de tipo genérico. La implementación de tres, cuatro, y así sucesivamente los parámetros de tipo es directa.

public abstract class Union<T1, T2>
{
    public abstract int TypeSlot
    {
        get;
    }

    public virtual T1 AsT1()
    {
        throw new TypeAccessException(string.Format(
            "Cannot treat this instance as a {0} instance.", typeof(T1).Name));
    }

    public virtual T2 AsT2()
    {
        throw new TypeAccessException(string.Format(
            "Cannot treat this instance as a {0} instance.", typeof(T2).Name));
    }

    public static implicit operator Union<T1, T2>(T1 data)
    {
        return new FromT1(data);
    }

    public static implicit operator Union<T1, T2>(T2 data)
    {
        return new FromT2(data);
    }

    public static implicit operator Union<T1, T2>(Tuple<T1, T2> data)
    {
        return new FromTuple(data);
    }

    public static implicit operator T1(Union<T1, T2> source)
    {
        return source.AsT1();
    }

    public static implicit operator T2(Union<T1, T2> source)
    {
        return source.AsT2();
    }

    private class FromT1 : Union<T1, T2>
    {
        private readonly T1 data;

        public FromT1(T1 data)
        {
            this.data = data;
        }

        public override int TypeSlot 
        { 
            get { return 1; } 
        }

        public override T1 AsT1()
        { 
            return this.data;
        }

        public override string ToString()
        {
            return this.data.ToString();
        }

        public override int GetHashCode()
        {
            return this.data.GetHashCode();
        }
    }

    private class FromT2 : Union<T1, T2>
    {
        private readonly T2 data;

        public FromT2(T2 data)
        {
            this.data = data;
        }

        public override int TypeSlot 
        { 
            get { return 2; } 
        }

        public override T2 AsT2()
        { 
            return this.data;
        }

        public override string ToString()
        {
            return this.data.ToString();
        }

        public override int GetHashCode()
        {
            return this.data.GetHashCode();
        }
    }

    private class FromTuple : Union<T1, T2>
    {
        private readonly Tuple<T1, T2> data;

        public FromTuple(Tuple<T1, T2> data)
        {
            this.data = data;
        }

        public override int TypeSlot 
        { 
            get { return 0; } 
        }

        public override T1 AsT1()
        { 
            return this.data.Item1;
        }

        public override T2 AsT2()
        { 
            return this.data.Item2;
        }

        public override string ToString()
        {
            return this.data.ToString();
        }

        public override int GetHashCode()
        {
            return this.data.GetHashCode();
        }
    }
}

Y mi intento de solución mínima todavía ampliable usando anidación de la Unión / Cualquiera de los tipos . También el uso de parámetros por defecto en el método de ajuste permite naturalmente "X o por defecto" escenario.

using System;
using System.Reflection;
using NUnit.Framework;

namespace Playground
{
    [TestFixture]
    public class EitherTests
    {
        [Test]
        public void Test_Either_of_Property_or_FieldInfo()
        {
            var some = new Some(false);
            var field = some.GetType().GetField("X");
            var property = some.GetType().GetProperty("Y");
            Assert.NotNull(field);
            Assert.NotNull(property);

            var info = Either<PropertyInfo, FieldInfo>.Of(field);
            var infoType = info.Match(p => p.PropertyType, f => f.FieldType);

            Assert.That(infoType, Is.EqualTo(typeof(bool)));
        }

        [Test]
        public void Either_of_three_cases_using_nesting()
        {
            var some = new Some(false);
            var field = some.GetType().GetField("X");
            var parameter = some.GetType().GetConstructors()[0].GetParameters()[0];
            Assert.NotNull(field);
            Assert.NotNull(parameter);

            var info = Either<ParameterInfo, Either<PropertyInfo, FieldInfo>>.Of(parameter);
            var name = info.Match(_ => _.Name, _ => _.Name, _ => _.Name);

            Assert.That(name, Is.EqualTo("a"));
        }

        public class Some
        {
            public bool X;
            public string Y { get; set; }

            public Some(bool a)
            {
                X = a;
            }
        }
    }

    public static class Either
    {
        public static T Match<A, B, C, T>(
            this Either<A, Either<B, C>> source,
            Func<A, T> a = null, Func<B, T> b = null, Func<C, T> c = null)
        {
            return source.Match(a, bc => bc.Match(b, c));
        }
    }

    public abstract class Either<A, B>
    {
        public static Either<A, B> Of(A a)
        {
            return new CaseA(a);
        }

        public static Either<A, B> Of(B b)
        {
            return new CaseB(b);
        }

        public abstract T Match<T>(Func<A, T> a = null, Func<B, T> b = null);

        private sealed class CaseA : Either<A, B>
        {
            private readonly A _item;
            public CaseA(A item) { _item = item; }

            public override T Match<T>(Func<A, T> a = null, Func<B, T> b = null)
            {
                return a == null ? default(T) : a(_item);
            }
        }

        private sealed class CaseB : Either<A, B>
        {
            private readonly B _item;
            public CaseB(B item) { _item = item; }

            public override T Match<T>(Func<A, T> a = null, Func<B, T> b = null)
            {
                return b == null ? default(T) : b(_item);
            }
        }
    }
}

Se podría lanzar excepciones una vez que hay un intento de las variables de acceso que no se han inicializado, es decir, si se crea con un parámetro A y más adelante hay un intento de acceso B o C, podría lanzar, por ejemplo, UnsupportedOperationException. Se necesitaría un captador para que funcione sin embargo.

Puede exportar una función de emparejamiento pseudo-patrón, al igual que yo uso para el tipo O en mi Sasa biblioteca . Hay actualmente en tiempo de ejecución por encima, pero al final me la intención de añadir un análisis CIL a inline todos los delegados en una declaración de caso real.

No es posible hacer exactamente con la sintaxis que se ha utilizado, pero con un poco más de la verbosidad y copiar / pegar que es fácil de hacer resolución de sobrecarga hacer el trabajo para usted:


// this code is ok
var u = new Union("");
if (u.Value(Is.OfType()))
{
    u.Value(Get.ForType());
}

// and this one will not compile
if (u.Value(Is.OfType()))
{
    u.Value(Get.ForType());
}

A estas alturas debería ser bastante obvio cómo ponerlo en práctica:


    public class Union
    {
        private readonly Type type;
        public readonly A a;
        public readonly B b;
        public readonly C 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;
        }

        public bool Value(TypeTestSelector _)
        {
            return typeof(A) == type;
        }

        public bool Value(TypeTestSelector _)
        {
            return typeof(B) == type;
        }

        public bool Value(TypeTestSelector _)
        {
            return typeof(C) == type;
        }

        public A Value(GetValueTypeSelector _)
        {
            return a;
        }

        public B Value(GetValueTypeSelector _)
        {
            return b;
        }

        public C Value(GetValueTypeSelector _)
        {
            return c;
        }
    }

    public static class Is
    {
        public static TypeTestSelector OfType()
        {
            return null;
        }
    }

    public class TypeTestSelector
    {
    }

    public static class Get
    {
        public static GetValueTypeSelector ForType()
        {
            return null;
        }
    }

    public class GetValueTypeSelector
    {
    }

No hay controles para extraer el valor del tipo equivocado, por ejemplo:.


var u = Union(10);
string s = u.Value(Get.ForType());

Así que se podría considerar la adición de controles necesarios y lanzar excepciones en estos casos.

Yo uso propio de la Unión Type.

Considere un ejemplo para hacerlo más claro.

Imaginemos que tenemos contacto clase:

public class Contact 
{
    public string Name { get; set; }
    public string EmailAddress { get; set; }
    public string PostalAdrress { get; set; }
}

Estos son definidos como cadenas simples, pero realmente ¿Son sólo las cadenas? Por supuesto no. El nombre puede consistir Primer nombre y apellido. O es un correo electrónico sólo un conjunto de símbolos? Sé que por lo menos debe contener @ y es necesariamente.

Vamos a nosotros mejorar el modelo de dominio

public class PersonalName 
{
    public PersonalName(string firstName, string lastName) { ... }
    public string Name() { return _fistName + " " _lastName; }
}

public class EmailAddress 
{
    public EmailAddress(string email) { ... } 
}

public class PostalAdrress 
{
    public PostalAdrress(string address, string city, int zip) { ... } 
}

En estas clases serán las validaciones durante la creación y finalmente tendrá modelos válidos. Consturctor en clase PersonaName requiere nombre y apellido al mismo tiempo. Esto significa que después de la creación, que pueden no tener estado no válido.

Y la clase de contacto, respectivamente

public class Contact 
{
    public PersonalName Name { get; set; }
    public EmailAdress EmailAddress { get; set; }
    public PostalAddress PostalAddress { get; set; }
}

En este caso tenemos el mismo problema, objeto de la clase de contacto puede estar en estado no válido. Quiero decir que puede tener EmailAddress pero tienen no Nombre

var contact = new Contact { EmailAddress = new EmailAddress("foo@bar.com") };

Vamos a fijar y crear la clase de contacto con el constructor que requiere PersonalName, EmailAddress y PostalAddress:

public class Contact 
{
    public Contact(
               PersonalName personalName, 
               EmailAddress emailAddress,
               PostalAddress postalAddress
           ) 
    { 
         ... 
    }
}

Pero aquí tenemos otro problema. ¿Qué pasa si la persona tiene solamente emailadress y no tener PostalAddress?

Si pensamos en ello no nos damos cuenta de que hay tres posibilidades de estado válido de Contacto objeto de clase:

  1. Un contacto sólo tiene una dirección de correo electrónico
  2. Un contacto sólo tiene una dirección postal
  3. Un contacto tiene tanto una dirección de correo electrónico y una dirección postal

Vamos a escribir a cabo modelos de dominio. Para el inicio vamos a crear la clase de contacto que se afirma se corresponde con los casos anteriores.

public class ContactInfo 
{
    public ContactInfo(EmailAddress emailAddress) { ... }
    public ContactInfo(PostalAddress postalAddress) { ... }
    public ContactInfo(Tuple<EmailAddress,PostalAddress> emailAndPostalAddress) { ... }
}

y el contacto de clase:

public class Contact 
{
    public Contact(
              PersonalName personalName,
              ContactInfo contactInfo
           )
    {
        ...
    }
}

try de uso Let él:

var contact = new Contact(
                  new PersonalName("James", "Bond"),
                  new ContactInfo(
                      new EmailAddress("agent@007.com")
                  )
               );
Console.WriteLine(contact.PersonalName()); // James Bond
Console.WriteLine(contact.ContactInfo().???) // here we have problem, because ContactInfo have three possible state and if we want print it we would write `if` cases

método add Partido Vamos en clase ContactInfo

public class ContactInfo 
{
   // constructor 
   public TResult Match<TResult>(
                      Func<EmailAddress,TResult> f1,
                      Func<PostalAddress,TResult> f2,
                      Func<Tuple<EmailAddress,PostalAddress>> f3
                  )
   {
        if (_emailAddress != null) 
        {
             return f1(_emailAddress);
        } 
        else if(_postalAddress != null)
        {
             ...
        } 
        ...
   }
}
  

En el método de partido, podemos escribir el código, porque el estado de la clase de contacto se controla con los constructores y puede tener sólo uno de los posibles estados.

Vamos a crear una clase auxiliar, por lo que cada vez que no escriba ya que muchos de código.

public abstract class Union<T1,T2,T3>
    where T1 : class
    where T2 : class
    where T3 : class
{
    private readonly T1 _t1;
    private readonly T2 _t2;
    private readonly T3 _t3;
    public Union(T1 t1) { _t1 = t1; }
    public Union(T2 t2) { _t2 = t2; }
    public Union(T3 t3) { _t3 = t3; }

    public TResult Match<TResult>(
            Func<T1, TResult> f1,
            Func<T2, TResult> f2,
            Func<T3, TResult> f3
        )
    {
        if (_t1 != null)
        {
            return f1(_t1);
        }
        else if (_t2 != null)
        {
            return f2(_t2);
        }
        else if (_t3 != null)
        {
            return f3(_t3);
        }
        throw new Exception("can't match");
    }
}
  

Podemos tener una clase tal de antemano por varios tipos, como se hace con los delegados Func, Acción. 4-6 parámetros de tipo genérico serán en su totalidad para la clase Unión.

reescritura clase ContactInfo Vamos a:

public sealed class ContactInfo : Union<
                                     EmailAddress,
                                     PostalAddress,
                                     Tuple<EmaiAddress,PostalAddress>
                                  >
{
    public Contact(EmailAddress emailAddress) : base(emailAddress) { }
    public Contact(PostalAddress postalAddress) : base(postalAddress) { }
    public Contact(Tuple<EmaiAddress, PostalAddress> emailAndPostalAddress) : base(emailAndPostalAddress) { }
}
  

A continuación, el compilador le pedirá anulación durante al menos un constructor. Si nos olvidamos de anular el resto de los constructores no podemos crear objeto de la clase ContactInfo con otro estado. Esto nos protegerá de excepciones de tiempo de ejecución durante el juego.

var contact = new Contact(
                  new PersonalName("James", "Bond"),
                  new ContactInfo(
                      new EmailAddress("agent@007.com")
                  )
               );
Console.WriteLine(contact.PersonalName()); // James Bond
Console
    .WriteLine(
        contact
            .ContactInfo()
            .Match(
                (emailAddress) => emailAddress.Address,
                (postalAddress) => postalAddress.City + " " postalAddress.Zip.ToString(),
                (emailAndPostalAddress) => emailAndPostalAddress.Item1.Name + emailAndPostalAddress.Item2.City + " " emailAndPostalAddress.Item2.Zip.ToString()
            )
    );

Eso es todo. Espero que hayan disfrutado.

Ejemplo tomado del sitio F # para la diversión y el beneficio

El equipo de diseño del lenguaje C # discutió discrimina sindicatos en enero de 2017 https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-01-10.md#discriminated-unions- via-cerrado-tipos

Se puede votar a favor de la solicitud de función en https://github.com/dotnet/csharplang / temas / 113

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top