我有以下一组枚举:
[Flags]
public enum Categories : uint
{
A = (1 << 0),
B = (1 << 1),
B1 = B | (1 << 16),
B2 = B | (1 << 17),
B3 = B | (1 << 18),
B4 = B | (1 << 19),
B5 = B | (1 << 20),
C = (1 << 2),
C1 = C | (1 << 21),
D = (1 << 3),
D1 = D | (1 << 22),
D2 = D | (1 << 23),
E = (1 << 4),
F = (1 << 5),
F1 = F | (1 << 23),
F2 = F | (1 << 24),
F3 = F | (1 << 25),
F4 = F | (1 << 26),
F5 = F | (1 << 27),
G = (1 << 6),
H = (1 << 7),
H1 = H | (1 << 28),
}
其思想是枚举代表一个层次结构,其中子枚举暗示其父枚举,并且可以应用任意数量的标志。
我看到的问题是,在调试期间,所有子枚举都没有被表示为名称或名称集。即,Categories.F
= "F"但Categories.F2
= 16777248。我希望Categories.F2
= "F, F2"或至少"F2"
如何使枚举保持被识别为标志?有没有更好的方法来完成我想做的事情?
非常奇怪,调试器中的值与ToString
值不同。根据文档,这两个应该匹配(因为Enum
类型确实覆盖了ToString
)。
如果c#对象有覆盖的
ToString()
,调试器将调用覆盖并显示其结果,而不是标准的{<typeName>}
。
显然这不适用于枚举。我最好的猜测是调试器试图对枚举类型做一些特殊的、未记录的处理。添加DebuggerDisplayAttribute
显然通过覆盖此行为解决了这个问题。
[DebuggerDisplay("{ToString()}")]
[Flags]
public enum Categories : uint
{
...
}
Categories.F2.ToString() = "F, F2"
c#不会为你做这个神奇的事情,因为F2
在枚举中已经有了自己的名字。您可以像这样手动标记单个成员:
public enum Categories
{
[Description("F, F2")]
F2 = F | (1 << 24),
}
然后编写代码转换为描述。
public static string ToDescription(this Categories c)
{
var field = typeof(Categories).GetField(c.ToString());
if (field != null)
{
return field.GetCustomAttributes().Cast<DescriptionAttribute>().First().Description;
}
}
...
Categories.F2.ToDescription() == "F, F2";
或者你也可以用魔法自己生成:
public static string ToDescription(this Categories c)
{
var categoryNames =
from v in Enum.GetValues(typeof(Categories)).Cast<Category>()
where v & c == c
orderby v
select v.ToString();
return String.Join(", ", categoryNames);
}
不幸的是,扩展方法不能与DebuggerDisplayAttribute
一起使用,但您可以使用DebuggerTypeAttribute
, YMMV,但您可以尝试:
[DebuggerType("CategoryDebugView")]
[Flags]
public enum Categories : uint
{
...
}
internal class CategoryDebugView
{
private Category value;
public CategoryDebugView(Category value)
{
this.value = value;
}
public override string ToString()
{
var categoryNames =
from v in Enum.GetValues(typeof(Categories)).Cast<Category>()
where v & c == c
orderby v
select v.ToString();
return String.Join(", ", categoryNames);
}
}
您只需做一点工作就可以完成您的要求。我在Categories
上创建了一些扩展方法,使用HasFlag()
来确定枚举值是否具有特定的父值,然后在它们上调用ToString()
并连接结果。
public static class CategoriesExtensionMethods
{
public static Categories GetParentCategory(this Categories category)
{
Categories[] parents =
{
Categories.A,
Categories.B,
Categories.C,
Categories.D,
Categories.E,
Categories.F,
Categories.G,
Categories.H,
};
Categories? parent = parents.SingleOrDefault(e => category.HasFlag(e));
if (parent != null)
return (Categories)parent;
return Categories.None;
}
public static string ToStringWithParent(this Categories category)
{
var parent = GetParentCategory(category);
if (parent == Categories.None)
return category.ToString();
return string.Format("{0} | {1}", parent.ToString(), category.ToString());
}
}
那么我们可以这样使用:
var f1 = Categories.F1;
var f1ParentString = f1.ToStringWithParent();
// f1ParentString = "F | F1"
var f = Categories.F;
var fParentString = f.GetParentCategory();
// fParentString = "F"
更新如果你不想指定所有的父节点,这里有一个更漂亮的实现GetParentCategory()
的方法。
public static Categories GetParentCategory(this Categories category)
{
var values = Enum.GetValues(typeof(Categories)).Cast<Categories>();
var parent = values.SingleOrDefault(e => category.HasFlag(e) && e != Categories.None && category != e);
if (parent != Categories.None)
return (Categories)parent;
return Categories.None;
}