将标记的枚举转换为另一个枚举

Convert a flagged enum to another one

我有一个通用枚举,假设 G 有一些标记值(一 = 0 / 二 = 1 / 三 = 2 / 四 = 4 / 五 = 8,等等)。

然后我有另一个枚举(假设 B)"extends" G 具有这种模式:一个 = G.One / 二 = G.Two / 三 = G.Three / 四 = G.Four(仅此而已,没有五)。

我终于有了最后一个枚举(假设是 C),它也 "extends" G 具有相同的模式类型但其他值:三 = G.Three / 四 = G.Four /五 = G.Five(没有一和二)。

我想找到一个通用函数来将 B 转换为 C 或将 C 转换为 B。 例如,如果我有 "A valsAsA = A.One | A.Three | A.Four",我想要一个像这样的函数:"B valsAsB = convert(valsAsA);" 会给我 "B.Three | A.Four".

这应该是非常通用的,因为我不仅有 A 和 B 枚举,还有 C、D、E... 具有不同的可能枚举值,但值始终来自通用枚举。

是否可以在每次添加新枚举时不检查所有可能性并调整函数?

一个例子:

    public enum General : int
    {
        One = 0,
        Two = 1,
        Three = 2,
        Four = 4,
        Five = 8
    }

    public enum A : int
    {
        One = General.One,
        Two = General.Two,
        Three = General.Three,
        Four = General.Four,
    }

    public enum B : int
    {
        Three = General.Three,
        Four = General.Four,
        Five = General.Five
    }

    public enum C : int
    {
        One = General.One,
        Three = General.Three,
        Five = General.Five
    }

    public class Test
    {
        public void testConvert()
        {
            A valAsA = A.One | A.Three | A.Four;
            B valAsB = convertFct(valAsA); // Should give me "B.Three | B.Four"
            C valAsC = convertFct(valAsA); // Should give me "C.One | C.Three"
        }
    }

我测试过:

A valAsA = A.One | A.Three | A.Four; 
C valAsC = (C)valAsA;
C valAsCReal = C.One | C.Three; // expected result

运气不好.. valAsC = 6 而 valAsCReal = 2...

非常感谢

妈的!!!我可以创建这个令人难以置信的函数来回答我的问题,但请...告诉我还有更优雅的东西...xD

        private TRet ConvertIt<TRet, TOrig>(TOrig values) where TOrig : struct, IConvertible where TRet : struct, IConvertible
        {
            if (!typeof(TOrig).IsEnum || 
                !typeof(TRet).IsEnum ||
                !typeof(int).IsAssignableFrom(typeof(TOrig)) || 
                !typeof(int).IsAssignableFrom(typeof(TRet)))
            {
                throw new ArgumentException("TOrig and TRet must be an enumerated type extending integer");
            }

            bool retEnumHasZero = false;

            foreach (var flag in Enum.GetValues(typeof(TRet)))
            {
                if ((int)flag == 0)
                {
                    retEnumHasZero = true;
                    break;
                }
            }

            if (!retEnumHasZero)
            {
                throw new ArgumentException("TRet enum must have the 0 flag");
            }

            Dictionary<int, Enum> valsOrig = new Dictionary<int, Enum>();

            foreach (var flag in Enum.GetValues(typeof(TOrig)))
            {
                valsOrig.Add((int)flag, (Enum)flag);
            }

            object valuesAsObject = values;
            var valuesAsEnum = (Enum)valuesAsObject;


            int returnedValue = 0;

            foreach (var flag in Enum.GetValues(typeof(TRet)))
            {
                int flagAsInt = (int)flag;

                if (valsOrig.ContainsKey(flagAsInt) && valuesAsEnum.HasFlag(valsOrig[flagAsInt]))
                {
                    returnedValue |= flagAsInt;
                }
            }

            return (TRet)Enum.ToObject(typeof(TRet), returnedValue);
        }

使用函数:

A valAsA = A.One | A.Two | A.Three | A.Four;
C valAsC = ConvertIt<C, A>(valAsA);

编辑:这个实现看起来更好:

        private T ConvertIt<T>(Enum values) where T : struct, IConvertible
        {
            if (!typeof(T).IsEnum)
            {
                throw new ArgumentException("Type to return must be an enumerated type");
            }

            if (!Enum.IsDefined(typeof(T), 0))
            {
                throw new ArgumentException("Type to return enum must have the 0 flag");
            }

            int returnedValue = 0;

            foreach (var flag in Enum.GetValues(values.GetType()))
            {
                int flagAsInt = (int)flag;

                if (values.HasFlag((Enum)flag) && Enum.IsDefined(typeof(T), flagAsInt))
                {
                    returnedValue |= flagAsInt;
                }
            }

            return (T)Enum.ToObject(typeof(T), returnedValue);
        }

使用函数:

A valAsA = A.One | A.Two | A.Three | A.Four;
C valAsC = ConvertIt<C>(valAsA);

最后,在John Wu的帮助下,这里是最终的功能=>

        private T ConvertIt<T>(Enum input) where T : struct, IConvertible
        {
            if (!typeof(T).IsEnum)
            {
                throw new ArgumentException("Type to return must be an enumerated type");
            }

            return (T)Enum.ToObject(typeof(T), (int)(object)input & Enum.GetValues(typeof(T)).Cast<int>().Sum());
        }

使用泛型执行此操作有点棘手,因为通常无法设置允许枚举的类型约束(请参阅 this question)。你能做的最好的事情就是限制 struct, IConvertible 并进行运行时检查,就像我在这个例子中所做的那样。

如果你能应付那部分的丑陋,剩下的就相当简单了:

首先,编写两个方法与 General 进行相互转换。由于您的枚举是位掩码,因此 "conversion" 实际上只是针对所有可能值之和的二进制 and 操作,您可以使用 GetValues.

获得

执行完 and 操作后,您可以通过使用 Enum.ToObject().

转换整数来 return 适当类型的枚举
static public class ExtensionMethods
{
    static public General ToGeneral<T>(this T input) where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum) throw new ArgumentException("Input must be an enum.");

        return (General)((int)(object)input & Enum.GetValues(typeof(General)).Cast<int>().Sum());
    }

    static public T ToEnum<T>(this General input)
    {
        if (!typeof(T).IsEnum) throw new ArgumentException("Output type must be an enum.");

        return (T)Enum.ToObject(typeof(T), (int)input & Enum.GetValues(typeof(T)).Cast<int>().Sum());
    }
}

写完这些后,与任何枚举之间的转换就很容易了:

static public TOut Convert<TIn,TOut>(TIn input) where TIn : struct, IConvertible where TOut: struct, IConvertible
{
    var general = input.ToGeneral();
    return general.ToEnum<TOut>();
}

测试代码:

public static void Main()
{
    A valAsA = A.One | A.Three | A.Four;
    B valAsB = Convert<A, B>(valAsA);  // Should give me "B.Three | B.Four"
    C valAsC = Convert<A, C>(valAsA); // Should give me "C.One | C.Three"

    Console.WriteLine("{0} should equal {1}", valAsB, (B.Three | B.Four));
    Console.WriteLine("{0} should equal {1}", valAsC, (C.One | C.Three));
}

输出:

6 should equal 6
Three should equal Three

See the code in action at DotNetFiddle