【问题标题】:Loop through bitwise enum values which have just a single bit field循环遍历只有一个位字段的按位枚举值
【发布时间】:2017-06-23 06:00:58
【问题描述】:

我在类似于以下代码中定义了多个标志枚举

[Flags]
public enum Colors
{
   None = 0,
   Red = 1,
   Green = 2,
   Blue = 4, 
   Purple = Red | Blue,
   Brown = Red | Green,
}

以下代码产生以下输出

Colors color1 = Colors.Red | Colors.Blue;
Colors color2 = Colors.Purple;
string s1 = color1.ToString(); // Sets s1 to "Purple"
string s2 = color2.ToString(); // Sets s2 to "Purple"



我想要一种输出按位枚举的各个位的方法,即使定义了匹配的组合。

private void Foo()
{
  Colors color1 = Colors.Red | Colors.Blue;
  Colors color2 = Colors.Purple;
  string s1 = CreateColumnString(color1); // Sets s1 to "Red|Blue"
  string s2 = CreateColumnString(color2); // Sets s2 to "Red|Blue"
}

我认为我可以遍历枚举的所有值并检查该值是否为 2 的幂。但我不知道如何获得 Enum 参数的潜在价值。

private string CreateColumnString(object value)
{
 //is this an enum with Flags attribute?
 if (value is Enum  && value.GetType().GetCustomAttributes(typeof(FlagsAttribute), true).Length > 0)
 {
    Enum e = (Enum)value;
    //Get a list of Enum values set in this flags enum
    IEnumerable<Enum> setValues = 
      Enum.GetValues(value.GetType())
          .Cast<Enum>()
          .Where(eachEnum => IsPowerOfTwo(eachEnum) && value.HasFlag(eachEnum)); 

    return string.Join("|", setValues);
 }
 else
 {
    return value != null ? value.ToString() : string.Empty;
 }
 return str;
}

private static bool IsPowerOfTwo(Enum e)
{
   int x = (int)e; //ERROR cannot convert type 'System.Enum' to 'ulong'
   return (x != 0) && ((x & (x - 1)) == 0);
}

【问题讨论】:

标签: c# reflection enums


【解决方案1】:

可能有更好的方法可以做到这一点,但这应该可以满足您的需求:

private static string AsString<T>(this T values)
{
    Enum v = (Enum)Convert.ChangeType(values, typeof(Enum));
    Array array = Enum.GetValues(typeof(T));
    IEnumerable<Enum> setFlags = array
        .Cast<Enum>()
        .Where(c => v.HasFlag(c) && IsDistinctValue(c));

    return values.Equals(default(T))
        ? default(T).ToString()
        : string.Join("|", setFlags.Where(c => Convert.ToInt32(c) != 0).Select(c => c.ToString()));
}

private static bool IsDistinctValue(Enum value)
{
    int current = Convert.ToInt32(value) >> 1;
    while (current > 0)
    {
        if ((Convert.ToInt32(value) & current) != 0)
        {
            return false;
        }
        current >>= 1;
    }

    return true;
}

它将基本上列出设置标志的值,除了那些“包含”其他标志的值。它通过获取正在测试的值,将其递减到零并检查原始值是否将该递减值设置为标志来计算出这一点。最后,它将删除“None”值,除非没有设置标志。

像这样使用它:

Colors c = Colors.Purple;
Console.WriteLine(c.AsString());

【讨论】:

  • 谢谢,但 Colors 枚举只是一个例子。我想要一种可以传递任何标志 Enum 的方法。
  • @Osiris 在那里,更新了代码以更通用。您可能需要添加其中一些检查以验证它确实是一个标志枚举。
  • @Osiris 不能放过它,所以我回来了,发现了一个错误并改进了它。现在它应该可以处理大多数场景(还有没有用 Flags 属性修饰的枚举)。
【解决方案2】:

这是另一种方法。我认为您拥有的选择越多越好:)

public static class EnumHelpers
{
    public static string ToStringExtended<T>(this Enum e)
    {
        if (!(e.GetType().GetCustomAttributes(typeof(FlagsAttribute), true).Length > 0))
            return e.ToString();

        List<string> eNames = new List<string>();
        foreach (T fish in Enum.GetValues(typeof(T)))
        {
            Enum num = fish as Enum;
            if (e.HasFlag(num) && Convert.ToInt32(fish) != 0 && Convert.ToInt32(fish) != Convert.ToInt32(e))
                eNames.Add(fish.ToString());
        }

        return eNames.Count > 1 ? String.Join("|", eNames.ToArray()) : e.ToString();
    }
}

用法几乎与 Fredirk 提出的相同:

Colors c = Colors.Purple;
Console.WriteLine(c.ToStringExtended<Colors>());
// Output : Red|Blue

【讨论】:

    【解决方案3】:

    您可以使用HasFlag method:

    .Where(e.HasFlag)
    

    但是,我认为您的 Enum.GetValues 调用也将获得您的枚举类型命名的多位值。

    编辑:

    这是另一种你可能会开始工作的方法:

    if (Enum.GetUnderlyingType(e.GetType()) != typeof(int))
      throw new NotImplementedException();
    
    var list = new List<Enum>();
    for (int i = 1; i != 0; i <<= 1)
    {
      var eachEnum = (Enum)(Enum.ToObject(e.GetType(), i));
      if (e.HasFlag(eachEnum))
        list.Add(eachEnum);
    }
    
    return string.Join(" | ", list);
    

    【讨论】:

    • 谢谢,HasFlag 似乎有效。现在我只需要验证当前值是 2 的幂。
    【解决方案4】:

    4 行代码中的答案,不包括方法签名。

        private static string CreateColumnString(Colors value)
        {
            // This is how we do it in embedded programming in C
            // In C we wouldn't need to convert, but alas in C# we do
            // So first ... 
            var num = Convert.ToByte(value);  
            // ToUint16 would work as well, but ToByte makes your intentions clearer
    
            // Then bitwise '& 'every bit position you care about to compose your string
            // For example: 0b0011 & 0b1111 = 0b0011    (3 AND 15 = 3)
            var s = (num & 1) > 0 ? "Red"   : "";
            s = (num & 2) > 0 ? s + "|Green": s;
            return (num & 2) > 0 ? s + "|Blue" : s;
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-01-09
      • 2023-03-31
      • 2010-12-12
      • 1970-01-01
      • 2011-03-24
      • 2012-11-08
      • 2011-05-07
      相关资源
      最近更新 更多