【问题标题】:Printing Flags Enum as Separate Flags将标志枚举打印为单独的标志
【发布时间】:2011-04-04 18:43:11
【问题描述】:

我有一个这样定义的标志枚举:

[Flags]
public enum MyEnum
{
    None =     0x00,
    Choice1 =  0x01,
    Choice2 =  0x02,
    Choice3 =  0x04,
    Default =  Choice1 | Choice2,
    All =      Default | Choice3
}

我想要一种方法来打印出MyEnum.Default 中包含哪些标志。在这种情况下,我希望输出类似于“Choice1,Choice2”。

简单打印MyEnum.Default.ToString() 的问题在于,当我想要“Choice1,Choice2”时,输出将是“默认”。

这是一个选项,但如果我使用它,我必须在每次更改枚举时更新打印。

((StudyData.Choice1 & StudyData.Default) == StudyData.Choice1 ? StudyData.Choice1.ToString() : "") + ", " +
((StudyData.Choice2 & StudyData.Default) == StudyData.Choice2 ? StudyData.Choice2.ToString() : "") + ", " +
((StudyData.Choice3 & StudyData.Default) == StudyData.Choice3 ? StudyData.Choice3.ToString() : "")

有人有更清洁的方法吗?理想情况下,我想要一种打印出 MyEnum.Default 中包含的标志的方法,而无需在每次添加新标志或更改默认值时更改打印代码。

谢谢!

【问题讨论】:

    标签: c# enums flags


    【解决方案1】:

    使用我在相关问题上写的here 的扩展方法,这应该很简单:

    var value = MyEnum.Default;
    var str = String.Join(", ", value.GetIndividualFlags());
    // "Choice1, Choice2"
    

    这是扩展方法:

    static class EnumExtensions
    {
        public static IEnumerable<Enum> GetFlags(this Enum value)
        {
            return GetFlags(value, Enum.GetValues(value.GetType()).Cast<Enum>().ToArray());
        }
    
        public static IEnumerable<Enum> GetIndividualFlags(this Enum value)
        {
            return GetFlags(value, GetFlagValues(value.GetType()).ToArray());
        }
    
        private static IEnumerable<Enum> GetFlags(Enum value, Enum[] values)
        {
            ulong bits = Convert.ToUInt64(value);
            List<Enum> results = new List<Enum>();
            for (int i = values.Length - 1; i >= 0; i--)
            {
                ulong mask = Convert.ToUInt64(values[i]);
                if (i == 0 && mask == 0L)
                    break;
                if ((bits & mask) == mask)
                {
                    results.Add(values[i]);
                    bits -= mask;
                }
            }
            if (bits != 0L)
                return Enumerable.Empty<Enum>();
            if (Convert.ToUInt64(value) != 0L)
                return results.Reverse<Enum>();
            if (bits == Convert.ToUInt64(value) && values.Length > 0 && Convert.ToUInt64(values[0]) == 0L)
                return values.Take(1);
            return Enumerable.Empty<Enum>();
        }
    
        private static IEnumerable<Enum> GetFlagValues(Type enumType)
        {
            ulong flag = 0x1;
            foreach (var value in Enum.GetValues(enumType).Cast<Enum>())
            {
                ulong bits = Convert.ToUInt64(value);
                if (bits == 0L)
                    //yield return value;
                    continue; // skip the zero value
                while (flag < bits) flag <<= 1;
                if (flag == bits)
                    yield return value;
            }
        }
    }
    

    【讨论】:

    • 请注意,如果您的枚举具有负值(例如使用 Visual Studio 可扩展性框架,Microsoft.VisualStudio.Shell.Interop._VSRDTFLAGS),那么在此处使用 ulong 和 ToUInt64 会导致问题。如果你用 long 和 ToInt64 替换,那么它将处理带有负值的枚举。
    【解决方案2】:

    FlagsAttribute 装饰你的枚举。它几乎完全符合您的要求:

    [Flags]
    public enum FooNum
    {
        foo = 0,
        bar = 1,
        lulz = 2,
        borkbork = 4
    }
    
    FooNum f = FooNum.bar | FooNum.borkbork;
    
    Debug.WriteLine(f.ToString());
    

    应该给你:

    酒吧,博克博克

    【讨论】:

    • 糟糕!我忘记在我的复制粘贴中包含 FlagsAttribute。我要解决原来的问题。感谢您的快速响应,很抱歉造成混乱......
    • 噢……我明白你现在在做什么了。这个问题是即使你用yourEnum = yourEnum.Choice1 | yourEnum.Choice2手动构造Default,当你把它推到一个字符串时,你仍然会得到yourEnum.Default。您需要枚举本身的 Default 和 All 定义有多严重?你将不得不像你已经发现的那样走位并将它们分开拉动。无论如何都会有点乱。另一种选择是将您的默认值存储为他们自己的 FooNum。不过,这本身就很混乱……
    • 可能让一些人看到这个问题的问题是,您需要为每个唯一的枚举值设置一个值,并且是 2 的幂,例如 1、2、4, 8 等。如果您不设置值,则隐含值将是 0、1、2、3 等。因此,当您将它们连接在一起(二进制 OR)时,它不会给出预期值。
    【解决方案3】:

    通过单个 linq 语句打印:

    var names = Enum.GetValues(typeof(MyEnum))
        .Cast<MyEnum>()
        .Where(a => (values & a) == a)
        .Select(a => a.ToString())
        .Aggregate((current, next) => current + ", " + next);
    

    更新版本只打印明确定义的值:

    var values = MyEnum.All;
    
    var allAttrs = Enum.GetValues(typeof(MyEnum)).Cast<MyEnum>();
    
    var names = allAttrs
        // leave only explicitly defined and not zero values
        .Where(attr => allAttrs.Count(a => a != 0 && (attr & a) == a) == 1)   
        .Where(a => (values & a) == a)
        .Select(a=>a.ToString())
        .Aggregate((current, next) => current + ", " + next);
    
    Console.WriteLine(names); // Choice1, Choice2, Choice3
    

    【讨论】:

      【解决方案4】:
      using System;
      using System.Collections.Generic;
      
      using System.Text;
      
      namespace printStar
      {
          class Program
          {
              static void Main(string[] args)
              {
      
      
                  Console.WriteLine("Enter the value ");
                  int k = int.Parse(Console.ReadLine());
                  int n = k - 1;
                  int x = 2 * (k - 1) + 1;
      
                  for (int p = 0; p <= n; p++)
                  {
                      for (int j = k - 1; j >= 0; j--)
                      {
                          Console.Write(" ");
                      }
      
                      for (int i = 0; i <= (x - 2 * (k - 1)); i++)
                      {
                          if (i % 2 == 1)
                          {
                              Console.Write("*");
                          }
                          else
                          {
                              Console.Write(" ");
                          }
                      }
      
                      Console.WriteLine();
                      k--;
                  }
                  Console.ReadLine();
              }
          }
      }
      

      【讨论】:

        【解决方案5】:

        我用最短、最清晰的代码解决了这个问题,我期望它表现良好,尽管在几个地方存在拳击。以你的类型为例:

        MyEnum e = MyEnum.Choice1 | MyEnum.Choice2;
        string s = FlagsEnumToString<MyEnum>(e); // Returns "Choice1, Choice2"
        

        这是它的实现方式:

        const string Separator = ", ";
        
        public static string FlagsEnumToString<T>(Enum e)
        {
            var str = new StringBuilder();
        
            foreach (object i in Enum.GetValues(typeof(T)))
            {
                if (IsExactlyOneBitSet((int) i) &&
                    e.HasFlag((Enum) i))
                {
                    str.Append((T) i + Separator);
                }
            }
        
            if (str.Length > 0)
            {
                str.Length -= Separator.Length;
            }
        
            return str.ToString();
        }
        
        static bool IsExactlyOneBitSet(int i)
        {
            return i != 0 && (i & (i - 1)) == 0;
        }
        

        可能会出现一些 cmets,我将首先解决这些问题:

        我需要调用你的方法同时提供类型变量吗?

        因为这不能通过通用T 参数隐式完成。 T 不能转换为 Enum 以与 HasFlag 一起使用。不,也不使用where T : struct, IConvertible

        foreach 也使用object?

        是的,还可以施放。只有object 可以转换为其他类型TintEnum

        我认为这可以通过在循环内使用临时变量强制转换为 int 来优化。

        我想是的,是的。为了清楚起见,这段代码是这样写的。所以是的,如果你愿意,可以这样做并摆脱那些 HasFlag 电话。

        我认为您仍然可以使用Enum 作为foreach 变量并节省转换时间。

        不,因为您需要转换为T,而这只能从object 完成。可能有“更好”的解决方案,但这肯定是最短、最清晰的解决方案。

        【讨论】:

          【解决方案6】:

          使用flags.ToString("g");
          Enumeration Format Strings

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多