显示可能的标志或修改以适应现有标志

时间:2014-06-03 16:17:00

标签: c# enums enum-flags

我有一段代码,我从API获取枚举标记。当此API更改和更新时,我可能会获得未在我的枚举结构中定义的值。

示例:

[Flags]
public enum TestEnum
{
   Enum1 = 1,
   Enum2 = 2,
   Enum3 = 4
}

当我说(TestEnum)7时,我得到输出所述数据时的字符串表示“Enum1 | Enum2 | Enum3”。但是,如果现在API发生了变化,并给了我一个用(TestEnum)15设置的附加标志“8”,String表示,特别是在Visual Studio中,不会给我标志,它只会显示“0xF”

有没有办法让所有正确映射的位被显示,或者像我的例子中那样,“忘记”Enum4,第四位。有没有办法做到这一点?

1 个答案:

答案 0 :(得分:0)

您需要做的是使用所有已定义值的组合来屏蔽该值,并对其执行“ToString()”,然后使用所有已定义值的补码来屏蔽该值,并对余数执行ToString():

    TestEnum value = (TestEnum)(64 - 1);
    var all = ((TestEnum[])Enum.GetValues(typeof(TestEnum))).Aggregate((TestEnum)0, (a, c) => a | c);
    var str = (value & all).ToString() + ((value & ~all) == 0 ? "" : ", 0x" + (value & ~all).ToString("X"));
    Debug.WriteLine(str);

输出

    Enum1, Enum2, Enum3, 0x00000038

将此作为通用扩展方法非常难,因为c#中没有通用enum约束,因此无法直接在枚举目标的任何通用方法中进行位掩码。这是一次尝试:

public static class EnumHelper
{
    // Generic singleton remembering basic properties about specified enums, cached for performance.
    sealed class DataSingleton<TEnum> where TEnum : struct, IConvertible, IComparable, IFormattable
    {
        static readonly DataSingleton<TEnum> instance = new DataSingleton<TEnum>();

        readonly bool isSigned;
        readonly TEnum allValues;
        readonly bool hasFlags;
        readonly bool isEnum;

        // Explicit static constructor to tell C# compiler
        // not to mark type as beforefieldinit
        static DataSingleton()
        {
        }

        DataSingleton()
        {
            var type = typeof(TEnum);
            isEnum = type.IsEnum;
            if (isEnum)
            {
                isSigned = GetIsSigned();
                allValues = GetAll();
                hasFlags = GetHasFlags();
            }
            else
            {
                isSigned = false;
                allValues = default(TEnum);
                hasFlags = false;
            }
        }

        static bool GetHasFlags()
        {
            var attributes = typeof(TEnum).GetCustomAttributes(typeof(FlagsAttribute), false);
            return attributes != null && attributes.Length > 0;
        }

        static bool GetIsSigned()
        {
            var underlyingType = Enum.GetUnderlyingType(typeof(TEnum));
            bool isSigned = (underlyingType == typeof(long) || underlyingType == typeof(int) || underlyingType == typeof(short) || underlyingType == typeof(sbyte));
            bool isUnsigned = (underlyingType == typeof(ulong) || underlyingType == typeof(uint) || underlyingType == typeof(ushort) || underlyingType == typeof(byte));
            if (!isSigned && !isUnsigned)
                throw new InvalidOperationException();
            return isSigned;
        }

        static TEnum GetAll()
        {
            if (GetIsSigned())
            {
                long value = 0;
                foreach (var v in Enum.GetValues(typeof(TEnum)))
                    // Not sure I need the culture but Microsoft passes it in Enum.ToUInt64(Object value) - http://referencesource.microsoft.com/#mscorlib/system/enum.cs
                    value |= Convert.ToInt64(v, CultureInfo.InvariantCulture);
                return (TEnum)Enum.ToObject(typeof(TEnum), value);
            }
            else
            {
                ulong value = 0;
                foreach (var v in Enum.GetValues(typeof(TEnum)))
                    // Not sure I need the culture but Microsoft passes it in Enum.ToUInt64(Object value) - http://referencesource.microsoft.com/#mscorlib/system/enum.cs
                    value |= Convert.ToUInt64(v, CultureInfo.InvariantCulture);
                return (TEnum)Enum.ToObject(typeof(TEnum), value);
            }
        }

        public bool HasFlags { get { return hasFlags; } }

        public bool IsSigned { get { return isSigned; } }

        public TEnum AllValues { get { return allValues; } }

        public bool IsEnum { get { return isEnum; } }

        public static DataSingleton<TEnum> Instance { get { return instance; } }
    }

    private static void ThrowOnNonEnum<TEnum>(DataSingleton<TEnum> data) where TEnum : struct, IConvertible, IComparable, IFormattable
    {
        if (!data.IsEnum)
        {
            throw (new ArgumentException("The generic argument [<TEnum>] must be an enumeration.", "TEnum: " + typeof(TEnum).FullName));
        }
    }

    private static void ThrowOnEnumWithFlags<TEnum>(DataSingleton<TEnum> data) where TEnum : struct, IConvertible, IComparable, IFormattable
    {
        if (!data.IsEnum || data.HasFlags)
        {
            throw (new ArgumentException("The generic argument [<TEnum>] must be an enumeration without the [FlagsAttribute] applied.", "TEnum: " + typeof(TEnum).FullName));
        }
    }

    private static void ThrowOnEnumWithoutFlags<TEnum>(DataSingleton<TEnum> data) where TEnum : struct, IConvertible, IComparable, IFormattable
    {
        if (!data.IsEnum || !data.HasFlags)
        {
            throw (new ArgumentException("The generic argument [<TEnum>] must be an enumeration with the [FlagsAttribute] applied.", "TEnum: " + typeof(TEnum).FullName));
        }
    }

    public static TEnum GetAll<TEnum>() where TEnum : struct, IConvertible, IComparable, IFormattable
    {
        var data = DataSingleton<TEnum>.Instance;
        ThrowOnEnumWithoutFlags<TEnum>(data);
        return data.AllValues;
    }

    static ulong ToUInt64<TEnum>(TEnum value) where TEnum : struct, IConvertible, IComparable, IFormattable
    {
        // Silently convert the value to UInt64 from the other base 
        // types for enum without throwing an exception.
        // This is need since the Convert functions do overflow checks.
        TypeCode typeCode = value.GetTypeCode();
        ulong result;

        switch (typeCode)
        {
            case TypeCode.SByte:
            case TypeCode.Int16:
            case TypeCode.Int32:
            case TypeCode.Int64:
                unchecked
                {
                    result = (UInt64)value.ToInt64(CultureInfo.InvariantCulture);
                }
                break;

            case TypeCode.Byte:
            case TypeCode.UInt16:
            case TypeCode.UInt32:
            case TypeCode.UInt64:
            case TypeCode.Boolean:
            case TypeCode.Char:
                unchecked
                {
                    result = value.ToUInt64(CultureInfo.InvariantCulture);
                }
                break;

            default:
                throw new InvalidOperationException();
        }
        return result;
    }

    public static string ToDebugString<TEnum>(this TEnum anEnum) where TEnum : struct, IConvertible, IComparable, IFormattable
    {
        var data = DataSingleton<TEnum>.Instance;
        if (!data.IsEnum || !data.HasFlags)
            return anEnum.ToString();
        var allLong = ToUInt64(data.AllValues);
        var enumLong = ToUInt64(anEnum);
        string str1 = ((TEnum)Enum.ToObject(typeof(TEnum), enumLong & allLong)).ToString(CultureInfo.InvariantCulture);
        var compliment = enumLong & ~(allLong);
        if (compliment == 0)
            return str1;
        return str1 + ", 0x" + ((TEnum)Enum.ToObject(typeof(TEnum), compliment)).ToString("X", CultureInfo.InvariantCulture);
    }
}