【问题标题】:List all bit names from a flag Enum列出标志枚举中的所有位名称
【发布时间】:2012-04-11 18:48:56
【问题描述】:

我正在尝试创建一个辅助方法来列出枚举值中设置的所有位的名称(用于记录目的)。我想要一个方法来返回在某些变量中设置的所有枚举值的列表。在我的例子中

[Flag]
Enum HWResponse
{
   None = 0x0,
   Ready = 0x1,
   Working = 0x2,
   Error = 0x80,
}

我喂它 0x81,它应该为我提供一个包含 {Ready, Error}IEnumerable<HWResponse>

由于没有找到更简单的方法,所以尝试编写下面的代码,但无法编译。

public static IEnumerable<T> MaskToList<T>(Enum mask) 
{
  if (typeof(T).IsSubclassOf(typeof(Enum)) == false)
    throw new ArgumentException();

  List<T> toreturn = new List<T>(100);

  foreach(T curValueBit in Enum.GetValues(typeof (T)).Cast<T>())
  {
    Enum bit = ((Enum) curValueBit);  // Here is the error

    if (mask.HasFlag(bit))
      toreturn.Add(curValueBit);
  }

  return toreturn;
}

在这个版本的代码中,编译器抱怨它不能将 T 转换为 Enum。

我做错了什么?有没有更好(更简单)的方法来做到这一点?我怎样才能制作演员表?

另外,我试着把方法写成

public static IEnumerable<T> MaskToList<T>(Enum mask) where T:Enum

但 Enum 是一种特殊类型,禁止使用 'where' 语法(使用 C# 4.0)

【问题讨论】:

  • 这似乎不应该是一个标志枚举;这些组合没有意义。可以同时“工作”和“就绪”吗?
  • @DBM:这是真的,这只是一个愚蠢的例子
  • @All:感谢您的精彩回答。三个都很有用!

标签: c# generics enums enumeration


【解决方案1】:

这是使用 LINQ 编写它的简单方法:

public static IEnumerable<T> MaskToList<T>(Enum mask)
{
    if (typeof(T).IsSubclassOf(typeof(Enum)) == false)
        throw new ArgumentException();

    return Enum.GetValues(typeof(T))
                         .Cast<Enum>()
                         .Where(m => mask.HasFlag(m))
                         .Cast<T>();
}

【讨论】:

  • 很好的解决方案:简单、易读且简短。但是有一个缺点:HasFlag(m) 还列出了状态“无”(0x0);但它很容易被旧式二进制文件 & 克服
  • 此外 HasFlag() 似乎有很大的性能问题:请参阅 stackoverflow.com/a/7164314/1121983 上的另一个线程
  • 这真的有效吗?当我尝试投射 var value = (T)Enum.GetValues(typeof(T)).Cast&lt;Enum&gt;().FirstOrDefault(m =&gt; mask.HasFlag(m)) 时,编译器会抱怨。
【解决方案2】:

如果您想要的最终结果是名称的字符串列表,只需调用mask.ToString()

如果枚举是这样定义的,你会怎么做:

[Flags]
enum State
{
    Ready = 1,
    Waiting = 2,
    ReadyAndWaiting = 3
}

至于解决编译器错误,应该这样做:

Enum bit = (Enum)(object)curValueBit;

Jon Skeet 有一个名为 unconstrained melody 的项目,它允许您在编译后通过重写 IL 来添加枚举约束。这是可行的,因为 CLR 支持这样的约束,即使 C# 不支持。

另一种想法:将GetValues的返回值直接转换为T[]会更高效:

foreach(T curValueBit in (T[])Enum.GetValues(typeof (T)))

【讨论】:

  • mask.ToString() 确实是我最终想要的(尽管我宁愿有一个更灵活的解决方案)。看起来在框架中实现了一个非常相似的代码来允许这样的结果。我想看看:)
  • 另外,我不喜欢“ReadyAndWaiting”解决方案:我的真实枚举有 14 个标志,我不会用这么长的名称实现所有可能的阶段 :)
  • 最后但并非最不重要的一点,你能解释一下 T[] 的演员阵容吗?性能问题?
  • @PPC 我也不是特别喜欢ReadyAndWaiting,但是如果你要处理你没有定义的枚举,你可能不得不处理这种可能性。否则,就没有意义了。至于性能,并没有我想象的那么糟糕:表达式(T[])Enum.GetValues(typeof (T))) 给你一个T[];数组 foreach 循环由 C# 编译器转换为更高效的 for 循环。如果你调用Cast,你最终会引用数组作为IEnumerable&lt;T&gt;,所以你使用的是IEnumerator&lt;T&gt;对象,它的效率略低。
  • @PPC 查看框架对ToString 的标志枚举实现,下载ILSpy 并反编译System.Enum.InternalFlagsFormat
【解决方案3】:

Gabe's answer 的基础上,我想出了这个:

public static class EnumHelper<T>
    where T : struct
{
    // ReSharper disable StaticFieldInGenericType
    private static readonly Enum[] Values;
    // ReSharper restore StaticFieldInGenericType
    private static readonly T DefaultValue;

    static EnumHelper()
    {
        var type = typeof(T);
        if (type.IsSubclassOf(typeof(Enum)) == false)
        {
            throw new ArgumentException();
        }
        Values = Enum.GetValues(type).Cast<Enum>().ToArray();
        DefaultValue = default(T);
    }

    public static T[] MaskToList(Enum mask, bool ignoreDefault = true)
    {
        var q = Values.Where(mask.HasFlag);
        if (ignoreDefault)
        {
            q = q.Where(v => !v.Equals(DefaultValue));
        }
        return q.Cast<T>().ToArray();
    }
}

我的组织方式有点不同,即我进行了类型检查(即:验证 T 是否真的是一个枚举)并在静态构造函数中获取枚举值,所以这只完成一次(这将是一个性能提升)。

另外,我添加了一个可选参数,因此您可以忽略枚举的典型“零”/“无”/“NotApplicable”/“未定义”/等值。

【讨论】:

    【解决方案4】:

    如果只是做这样的事情:

    public static IEnumerable<T> MaskToList<T>(Enum mask)
    {
     if (typeof(T).IsSubclassOf(typeof(Enum)) == false)
        throw new ArgumentException();
    
      List<T> toreturn = new List<T>(100);
    
      foreach(T curValueBit in Enum.GetValues(typeof (T)).Cast<T>())
      {
        Enum bit = (curValueBit as Enum);  // The only difference is actually here, 
                                           //  use "as", instead of (Enum) cast
    
        if (mask.HasFlag(bit))
          toreturn.Add(curValueBit);
      }
    
      return toreturn;
    }
    

    由于as 没有编译时检查。这里的编译器只是“相信”你,希望你知道自己在做什么,所以不会引发编译时错误

    【讨论】:

    • 为什么不foreach(Enum curValueBit in (T[])Enum.GetValues(typeof (T)))?此外,为了作为扩展的有用性,最好使用 T 掩码而不是 Enum 掩码
    • foreach 语句有效,但循环体需要修改,并且可能具有不同的性能特征。我怀疑转换为 T[] 而不是 .Cast 总是更好。我会担心 phoog 的情况,其定义的值具有多个标志。在这种情况下 HasFlag 做了什么?
    • @Random832:确切地说,你需要演员顺便说一句。我只是把自己的代码改了一行,就是..
    • @Random(第一个问题):我首先尝试使用 Ienumerable MaskToList(T mask),但在与编译器的斗争中将其遗弃了。它不允许我使用 mask.HasFlag() 并且我没有找到正确的演员表(寻找类似 "if ((mask&(T)bit) != 0)")
    • @Random832:事实上我没有使用T,而是使用了Enum 并且使用了as 运算符来使其编译和工作。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-26
    • 2018-01-14
    相关资源
    最近更新 更多