确定枚举值是否不是C#中的复合值

时间:2014-06-28 18:55:30

标签: c# .net enums enumeration enum-flags

修改

大多数人认为标志枚举应始终具有2的幂值。这可能是最佳实践,但我不是在这里定义枚举,而是检查它们并希望在合理范围内涵盖所有可能的场景。问题实际上是关于实现名为EnumUtilities.IsValueDefinedAndComposite<T>的函数的正确方法。

原始帖子:

考虑以下枚举:

[Flags]
public enum TestWithFlags { One = 1, Two = 2, }

以下是Enum.IsDefined的结果,其中各种值都投放为TestWithFlags

输出:

(1). Defined: True:  One.
(2). Defined: True:  Two.
(3). Defined: False: 100.
(4). Defined: False: One, Two.
(5). Defined: ?????: One, Two.

我无法弄清楚如何确定枚举值是否为复合值。请参阅以下代码中的函数EnumUtilities.IsValueDefinedAndComposite<T>

为方便起见,这是完整的代码。

using System;
using System.Collections.Generic;
using System.Linq;

namespace MyNamespace
{
    [Flags]
    public enum TestWithFlags { One = 1, Two = 2, }

    public static class Program
    {
        private static void Main (string [] args)
        {
            TestWithFlags value;

            value = TestWithFlags.One; // True.
            Console.WriteLine("(1). Defined: {0}:  {1}.", Enum.IsDefined(typeof(TestWithFlags), value), value.ToString());

            value = TestWithFlags.Two; // True.
            Console.WriteLine("(2). Defined: {0}:  {1}.", Enum.IsDefined(typeof(TestWithFlags), value), value.ToString());

            value = (TestWithFlags) 100; // False.
            Console.WriteLine("(3). Defined: {0}: {1}.", Enum.IsDefined(typeof(TestWithFlags), value), value.ToString());

            value = TestWithFlags.One | TestWithFlags.Two; // False.
            Console.WriteLine("(4). Defined: {0}: {1}.", Enum.IsDefined(typeof(TestWithFlags), value), value.ToString());

            value = TestWithFlags.One | TestWithFlags.Two; // Not implemented.
            Console.WriteLine("(5). Defined: N/A:   {1}.", EnumUtilities.IsValueDefinedAndComposite(value), value.ToString());

            Console.WriteLine();
            Console.Write("Press any key to continue...");
            Console.ReadKey(true);
        }
    }

    public static class EnumUtilities
    {
        public static List<T> GetValues<T> ()
            where T: struct, IComparable, IFormattable, IConvertible
        {
            EnumUtilities.ThrowOnNonEnum<T>();

            var list = Enum.GetValues(typeof(T)).OfType<T>().ToList().ConvertAll<T>(v => ((T) v));

            return (list);
        }

        public static bool IsValueDefinedAndComposite<T> (T value)
            where T: struct, IComparable, IFormattable, IConvertible
        {
            EnumUtilities.ThrowOnEnumWithoutFlags<T>();

            var values = EnumUtilities.GetValues<T>();

            var result = false;
            //var result = values.Count(v => (value | v) == value) > 1;
            // How to determine whether the argument [value] is composite.

            return (result);
        }

        public static bool IsValueDefinedAndNonComposite<T> (T value)
            where T: struct, IComparable, IFormattable, IConvertible
        {
            EnumUtilities.ThrowOnNonEnum<T>();

            return (Enum.IsDefined(typeof(T), value));
        }

        public static bool IsValueDefined<T> (T value)
            where T: struct, IComparable, IFormattable, IConvertible
        {
            EnumUtilities.ThrowOnNonEnum<T>();

            return (EnumUtilities.IsValueDefinedAndNonComposite(value) || EnumUtilities.IsValueDefinedAndComposite(value));
        }

        private static void ThrowOnNonEnum<T> ()
        {
            if (!typeof(T).IsEnum)
            {
                throw (new ArgumentException("The generic argument [<T>] must be an enumeration.", "T: " + typeof(T).FullName));
            }
        }

        private static void ThrowOnEnumWithFlags<T> ()
        {
            EnumUtilities.ThrowOnNonEnum<T>();

            var attributes = typeof(T).GetCustomAttributes(typeof(FlagsAttribute), false);

            if (attributes.Length > 0)
            {
                throw (new ArgumentException("The generic argument [<T>] must be an enumeration without the [FlagsAttribute] applied.", "T: " + typeof(T).FullName));
            }
        }

        private static void ThrowOnEnumWithoutFlags<T> ()
        {
            EnumUtilities.ThrowOnNonEnum<T>();

            var attributes = typeof(T).GetCustomAttributes(typeof(FlagsAttribute), false);

            if (attributes.Length == 0)
            {
                throw (new ArgumentException("The generic argument [<T>] must be an enumeration with the [FlagsAttribute] applied.", "T: " + typeof(T).FullName));
            }
        }
    }
}

7 个答案:

答案 0 :(得分:4)

您可以尝试类似(未经测试!):

   public static bool IsValueDefinedAndComposite<T>(T value)
        where T : struct, IComparable, IFormattable, IConvertible
    {
        EnumUtilities.ThrowOnEnumWithoutFlags<T>();

        var values = EnumUtilities.GetValues<T>();


        var result = values.OfType<T>().Contains(value);
        //var result = values.Count(v => (value | v) == value) > 1;
        // How to determine whether the argument [value] is composite.

        return (result);
    }

基本上,只是检查value参数是否是值的一部分,如果不是,则它是复合。

答案 1 :(得分:2)

回答你的问题;您可以通过检查是否是2的幂来检查标志值是单数值还是多个标志的组合。

请参阅How to check if a number is a power of 2

bool IsPowerOfTwo(ulong x)
{
     return (x != 0) && ((x & (x - 1)) == 0);
}

如果它不是,那么它有许多设置标志(因为每个标志必须是2的幂)。

答案 2 :(得分:1)

请注意,如果有问题的枚举是严格的位标志{One = 1<<0, Two = 1<< 1, ALot=1<<20},那么检查“单位设置”的其他答案会更合适。如果你的枚举可以包含多个位的掩码,请查看这个。即一些虚构的自定义“浮动”数字掩码显示为标志枚举{ Sign = 0x80, Mantissa=0x78, Power = 0x7}


测试值是否可以通过枚举中的某些值组合来表示:

一次性:只需开始为每个值删除一位,直到您运行值或结果为0。伪代码(重要的部分是& ~enumValue - 带有否定值的AND)

var remainingBits = value; 
foreach (var enumValue in GetAllValuesOfEnum(....))
{
  if (value == enumValue) return "ExisitngNonComposite";
  var remainingBits = current & ~enumValue;
  if (remainingBits == 0) return "Composite";
}
return "CanNotBeRepresented";    

如果您需要重复多次,只关心是否可以表示值:

  1. 获取枚举的所有值
  2. 或者他们在一起(对于合理的Flags枚举,int / long枚举值相应的值不会超过32/64),包括标志的常见组合,如<) LI>
  3. 如果获得全部(0xFFFFFFFF) - 可以表示任何值,否则(value & ~ allFilgesOrTogether) == 0会给你答案。

答案 3 :(得分:1)

如果你以稍微不同的方式处理它,这实际上很容易解决:使用F enum string format将枚举转换为字符串,然后检查结果字符串是否包含逗号。

来自Microsoft文档:

  

如果可能,将枚举条目显示为字符串值。如果   value可以完全显示为。中条目的总和   枚举(即使Flags属性不存在),字符串   每个有效条目的值连接在一起,用\ n分隔   逗号。如果该值不能完全确定   枚举条目,然后将值格式化为整数值。   以下示例说明了F格式说明符。

这将适用于所有枚举,无论它们是否定义了flags属性,因此在下面的代码更新中,我已将初始值测试更改为ThrowOnNonEnum。

以下是使用此方法实现您的方法:

    public static bool IsValueDefinedAndComposite<T>(T value)
        where T : struct, IComparable, IFormattable, IConvertible
    {
        EnumUtilities.ThrowOnNonEnum<T>();

        var valueAsString = Enum.Format(typeof (T), value, "F");

        // If the value contains a comma, then it is defined and composite
        if (valueAsString.Contains(","))
        {
            return true;
        }
        else
        {
            // If the value cannot be completely determined by the enumeration entries, it will be numeric. 
            // This is one possible method for testing this.
            double valueAsDouble = 0;
            return !(Double.TryParse(valueAsString, out valueAsDouble));
        }
    }

这是测试项目5的更新版本以及验证未完全定义的场景的新项目6:

 value = TestWithFlags.One | TestWithFlags.Two; // True
 Console.WriteLine("(5). Defined: {0}: {1}.", EnumUtilities.IsValueDefinedAndComposite(value), value.ToString());

 value = (TestWithFlags)6; // False
 Console.WriteLine("(6). Defined: {0}: {1}.", EnumUtilities.IsValueDefinedAndComposite(value), value.ToString());

输出:

(1). Defined: True:  One.
(2). Defined: True:  Two.
(3). Defined: False: 100.
(4). Defined: False: One, Two.
(5). Defined: True: One, Two.
(6). Defined: False: 6.

答案 4 :(得分:0)

您应该将枚举值设置为2的幂。

您可以通过执行(TestWithFlags) 2^NUMBER测试数字是否为枚举标记来测试它。

答案 5 :(得分:0)

如果我理解你的问题,那么你需要检查复合(flags)枚举是否包含特定值?您是否尝试使用按位操作?

if( 0 != (myCompositeEnum & TestWithFlags.One) )
{
    // then TestWithFalgs.One is in the composite.
}
顺便说一句,你的Enum值应该是2的幂 这是StackOverFlow post on a similar question

答案 6 :(得分:0)

情况可能稍微复杂一些,只是测试传入的值不是2的单个幂,因为定义的枚举标志值本身可以是其他定义的枚举值的组合,例如:

[Flags]
public enum TestWithFlags
{
    One = 1,
    Two = 2,
    Four = 4,
    Seven = Four | Two | One,
    Eight = 8,
    Nine = Eight | One,
}

那么,“复合”在这种情况下意味着什么?它是否意味着“两个或更多有效枚举值的掩码”,或者它是否意味着“不是定义的枚举值而是而是是两个或更多定义的枚举值的掩码”?

假设你想要“不是一个定义的枚举值,而是而是一个两个或多个定义的枚举值的掩码”,以下内容应该有效:

public static class EnumUtilities
{
    public static List<T> GetValues<T> ()
        where T: struct, IComparable, IFormattable, IConvertible
    {
        EnumUtilities.ThrowOnNonEnum<T>();

        var list = Enum.GetValues(typeof(T)).OfType<T>().ToList().ConvertAll<T>(v => ((T) v));

        return (list);
    }

    private static ulong[] GetValuesAsUint64<T>()
        where T : struct, IComparable, IFormattable, IConvertible
    {
        EnumUtilities.ThrowOnNonEnum<T>();
        IList eList = Enum.GetValues(typeof(T));
        ulong[] list = new ulong[eList.Count];
        for (int i = 0; i < eList.Count; i++)
        {
            list[i] = Convert.ToUInt64(eList[i]);
        }
        return list;
    }

    public static bool IsValueDefinedOrComposite<T>(T value)
        where T : struct, IComparable, IFormattable, IConvertible
    {
        EnumUtilities.ThrowOnEnumWithoutFlags<T>();

        var intValue = Convert.ToUInt64(value);
        var intValues = GetValuesAsUint64<T>();

        if (intValue == 0)
        {
            return intValues.Contains(intValue);
        }
        else
        {
            int matches = 0;

            foreach (var test in intValues)
            {
                if ((test & intValue) == test)
                {
                    matches++;
                    intValue &= ~(test);
                }
            }

            return matches > 0 && intValue == 0;
        }
    }

    public static bool IsValueDefinedAndNonComposite<T> (T value)
        where T: struct, IComparable, IFormattable, IConvertible
    {
        EnumUtilities.ThrowOnNonEnum<T>();

        return (Enum.IsDefined(typeof(T), value));
    }

    public static bool IsValueDefinedAndComposite<T>(T value)
        where T : struct, IComparable, IFormattable, IConvertible
    {
        return IsValueDefinedOrComposite(value) && !IsValueDefinedAndNonComposite(value);
    }

    private static void ThrowOnNonEnum<T> ()
    {
        if (!typeof(T).IsEnum)
        {
            throw (new ArgumentException("The generic argument [<T>] must be an enumeration.", "T: " + typeof(T).FullName));
        }
    }

    private static void ThrowOnEnumWithFlags<T> ()
    {
        EnumUtilities.ThrowOnNonEnum<T>();

        var attributes = typeof(T).GetCustomAttributes(typeof(FlagsAttribute), false);

        if (attributes.Length > 0)
        {
            throw (new ArgumentException("The generic argument [<T>] must be an enumeration without the [FlagsAttribute] applied.", "T: " + typeof(T).FullName));
        }
    }

    private static void ThrowOnEnumWithoutFlags<T> ()
    {
        EnumUtilities.ThrowOnNonEnum<T>();

        var attributes = typeof(T).GetCustomAttributes(typeof(FlagsAttribute), false);

        if (attributes.Length == 0)
        {
            throw (new ArgumentException("The generic argument [<T>] must be an enumeration with the [FlagsAttribute] applied.", "T: " + typeof(T).FullName));
        }
    }
}  

请注意,枚举的范围可以从字节大小到(u)长大;每个都应该检查(我没有 - 我只做了一些简单的测试。)。我觉得应该有一个更好的方法来做到这一点,但是enum实用程序自c#1.0以来没有改变,所以非常原始。零值的标志也需要进行测试。

最后,我发现将通用枚举转换为支持位掩码的类型很奇怪;我使用了this suggestion