4

编辑:

大多数人建议标志枚举应始终具有 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));
            }
        }
    }
}
4

7 回答 7

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 参数是否是值的一部分,如果不是,它是一个组合。

于 2014-06-28T19:04:23.560 回答
2

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

请参阅如何检查数字是否为 2 的幂

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

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

于 2014-06-28T19:07:57.893 回答
1

请注意,如果有问题的枚举是严格的位标志,那么{One = 1<<0, Two = 1<< 1, ALot=1<<20}检查“单个位集”的其他答案会更合适。如果您的枚举可以包含具有多个位的掩码,请查看这个。即一些假想的自定义“浮动”数字掩码以 Flags enum 的形式出现{ Sign = 0x80, Mantissa=0x78, Power = 0x7}


要测试值是否可以由枚举中的某些值组合表示:

一次性:只需开始为每个值删除位,直到您用完值或得到 0 作为结果。伪代码(重要部分是& ~enumValue- 与否定值)

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不会超过 32/64 )值,包括常见的标志组合,例如)intlong
  3. 如果全是 ( 0xFFFFFFFF) - 可以表示任何值,否则(value & ~ allFilgesOrTogether) == 0将再次给您答案。
于 2014-06-28T19:45:40.717 回答
1

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

从微软文档:

如果可能,将枚举条目显示为字符串值。如果该值可以完全显示为枚举中条目的总和(即使 Flags 属性不存在),则将每个有效条目的字符串值连接在一起,用逗号分隔。如果该值不能完全由枚举条目确定,则将该值格式化为整数值。以下示例说明了 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.
于 2014-06-29T01:26:52.887 回答
0

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

if( 0 != (myCompositeEnum & TestWithFlags.One) )
{
    // then TestWithFalgs.One is in the composite.
}

顺便说一句,您的 Enum 值应该是 2 的幂。
这是关于类似问题的 StackOverFlow 帖子

于 2014-06-28T19:04:17.847 回答
0

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

比你可以通过(TestWithFlags) 2^NUMBER测试一个数字是否是枚举标志来测试它。

于 2014-06-28T19:02:36.887 回答
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)long 大小;每个都应该检查(我没有 - 我只做了一些简单的测试。)。而且我觉得应该有更好的方法来做到这一点,但枚举实用程序自 c# 1.0 以来没有改变,因此非常原始。还需要测试具有零值的标志。

最后,我发现将通用枚举转换为支持位掩码的类型非常尴尬。我使用了这个建议

于 2014-06-28T21:02:10.147 回答