How should I present hierarchical flags?

I have the following set of enumerations:

[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), } 

The idea is that enumerations are a hierarchical structure in which a child enumeration implies a parent, and any number of flags can be applied.

The problem that I see is that all child enums are not displayed during debugging as names or sets of names. IE, Categories.F = "F", but Categories.F2 = 16777248. I would hope that Categories.F2 = "F, F2" or at least "F2"

How can I make my enums be saved as flags? Is there a better way to accomplish what I'm trying to do?

+4
source share
2 answers

It is very strange that the value in the debugger is different from the value of ToString . According to the documentation , they must match (because the Enum type really overrides ToString ).

If the C # object has an overridden ToString() , the debugger will call the override and display its result instead of the standard {<typeName>} .

Obviously this does not work for listings. My best guess is that the debugger is trying to do special, undocumented handling of enum types. Adding DebuggerDisplayAttribute seems to fix the problem by overriding this behavior.

 [DebuggerDisplay("{ToString()}")] [Flags] public enum Categories : uint { ... } 

Categories .F2.ToString () = "F, F2"

C # will not do this magic for you, because F2 already has its own name in the listing. You can manually mark individual items as follows:

 public enum Categories { [Description("F, F2")] F2 = F | (1 << 24), } 

And then write code to convert to a description.

 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"; 

Or you could do some magic to generate this yourself:

 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); } 

Unfortunately, the extension method cannot be used with DebuggerDisplayAttribute , but you can use DebuggerTypeAttribute , YMMV, but you can try the following:

 [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); } } 
+2
source

You can do what you ask with a little work. I created several extension methods on Categories that use HasFlag() to determine if an enumeration has a specific parent, and then calls ToString() on them and concatenates the result.

 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()); } } 

Then we can use it as follows:

 var f1 = Categories.F1; var f1ParentString = f1.ToStringWithParent(); // f1ParentString = "F | F1" var f = Categories.F; var fParentString = f.GetParentCategory(); // fParentString = "F" 

Update

Here's a prettier way to implement GetParentCategory() if you don't want to list all of your parents.

 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; } 
+1
source

All Articles