【问题标题】:Determine if an Enum value is NOT a composite in C#确定枚举值是否不是 C# 中的组合
【发布时间】:2014-06-28 18:55:30
【问题描述】:

编辑:

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

【问题讨论】:

  • 对于正确组合的标志枚举,不同的值都将是 2 的幂。
  • {One=1, Two=2, OneAndTwoMask=3} 的情况下 3 的结果应该是什么?
  • @PrestonGuillot 我不认为Flags 暗示所有值都是单独的位,而是打算用作位域。 IE。通常有像ReadOrWrite 或掩码PermissionBitsMask 这样的“帮助”值 - 所以看看枚举的多位值是否应该被OP视为复合值会很有趣。
  • @AviTurner - 我没有说它是好还是坏的设计 - 只是现有的设计,因此在编写实用程序函数时可能需要考虑它。示例 - FileShare
  • @AlexeiLevenkov “不同”我的意思是“那些不是复合的”,例如ReadWrite,但不是 ReadOrWrite。如果 OP 通过“复合”表示 else,我不相信 Flags 是他正在寻找的东西。

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


【解决方案1】:

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

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

【讨论】:

    【解决方案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 的幂)。

    【讨论】:

    • 这并不能真正回答问题。有关详细信息,请参阅@AlexeiLevenkov 评论。
    • 我又读了一遍。我仍然认为,如果一个编译的(已知的)枚举值解析为两个值的非幂,那么它是复合的。它是否是一个有效的复合然后将检查每个位是否解析为一个已知的编译枚举值。
    • 请注意,测试是在 EnumUtilities 类中完成的。正如@AlexeiLevenkov 所评论的,因为它是一个实用程序类,而不是引用编译时已知的枚举集it may need to be taken into account when writing utility functions
    【解决方案3】:

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


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

    一次性:只需开始为每个值删除位,直到您用完值或得到 0 作为结果。伪代码(重要部分是&amp; ~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,包括常见的标志组合)
    3. 如果得到所有值 (0xFFFFFFFF) - 可以表示任何值,否则 (value &amp; ~ allFilgesOrTogether) == 0 将再次给您答案。

    【讨论】:

    • 我认为你可以更好地利用这一点,因为任何组合都会设置 > 1 sig dig...所以我很确定你可以检查 >= 2 设置为 1 的位。
    • @DaveHaney 看起来 OP 编写了通用实用程序函数 - 因此可能需要处理元素设置为多个位(即掩码而不是单个位)的枚举。 IE。一些虚构的自定义“浮点”数字掩码显示为标志枚举 { Sign = 0x80, Mantissa=0x78, Power = 0x7}
    【解决方案4】:

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

    来自 Microsoft 文档:

    如果可能,将枚举条目显示为字符串值。如果 值可以完全显示为 枚举(即使标志属性不存在),字符串 每个有效条目的值连接在一起,由 逗号。如果该值不能完全由 枚举条目,然后将值格式化为整数值。 以下示例说明了 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.
    

    【讨论】:

      【解决方案5】:

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

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

      【讨论】:

        【解决方案6】:

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

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

        顺便说一句,您的 Enum 值应该是 2 的幂。
        这是StackOverFlow post on a similar question

        【讨论】:

          【解决方案7】:

          这种情况可能比仅仅测试传入的值不是 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) 长大小;每个都应该检查(我没有 - 我只做了一些简单的测试。)。而且我觉得应该有更好的方法来做到这一点,但枚举实用程序自 c# 1.0 以来没有改变,因此非常原始。零值的标志也需要测试。

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

          【讨论】:

          • 有没有办法检查一个值是否是直接赋值(即不是Nine = Eight | One)?这是一个Flags 枚举,所以我基本上是在寻找一个更好的enumValue.ToString().Contains(',') 替代品。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2015-02-12
          • 2019-01-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多