一尘不染

C#中的有区别的联合

c#

[注:此问题的原标题为“ C#中的C(ish)样式联合 ”,但正如Jeff的评论所知,这种结构显然称为“歧视联合”。

不好意思,这个问题。

在SO中已经有几个类似的问题需要解决,但是它们似乎集中在联合的内存节省优势或将其用于互操作方面。
这是一个这样的问题的例子

我对拥有工会类型的东西的渴望有所不同。

我现在正在编写一些代码,这些代码生成看起来像这样的对象

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

    public object ValueA;
    public object ValueB;
}

我认为您会同意一些相当复杂的内容。问题是,ValueA也只能是一些特定类型的(比方说stringintFoo(这是一个类),并ValueB可以是另一个小组的类型。我不喜欢处理这些值作为对象(我想要的温暖的贴身感觉的编码,并带有一些类型安全性)。

因此,我考虑编写一个琐碎的小包装器类,以表达ValueA在逻辑上是对特定类型的引用这一事实。我Union之所以打电话给全班,是因为我想达到的目标使我想起了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);
    }
}

使用此类ValueWrapper现在看起来像这样

public class ValueWrapper2
{
    public DateTime ValueCreationDate;
    public  Union<int, string, Foo> ValueA;
    public  Union<double, Bar, Foo> ValueB;
}

这与我想要实现的目标类似,但是我缺少一个相当关键的元素-即在调用Is和As函数时,编译器强制执行类型检查,如以下代码所示

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

IMO询问ValueA是否为Value是无效的,char因为其定义清楚地表明不是-
这是编程错误,我希望编译器对此有所了解。[另外,如果我能正确地做到这一点,(希望)我也将获得智能感知-这将是一个福音。

为了实现这一点,我想告诉编译器,该类型T可以是A,B或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;
    }

有谁知道我想要实现的目标是否可行?还是我只是因为一开始就写这个课程而变得愚蠢?

提前致谢。


阅读 259

收藏
2020-05-19

共1个答案

一尘不染

我真的不喜欢上面提供的类型检查和类型转换解决方案,因此这里是100%类型安全的联合,如果您尝试使用错误的数据类型,它将引发编译错误:

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);
            }
        }
    }
}
2020-05-19