我该如何表示分层标志枚举?

时间:2013-04-06 06:27:22

标签: c# enums enum-flags

我有以下一组枚举:

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

这个想法是枚举表示一个层次结构,其中子枚举意味着它的父节点,并且可以应用任意数量的标志。

我看到的问题是在调试期间没有将所有子枚举表示为名称或名称集。 I.E.,Categories.F =“F”但Categories.F2 = 16777248.我希望Categories.F2 =“F,F2”或至少“F2”

如何让我的枚举仍被识别为旗帜?有没有更好的方法来完成我想要做的事情?

2 个答案:

答案 0 :(得分:2)

调试器中的值与ToString值不同,这很奇怪。根据{{​​3}},两者应该匹配(因为Enum类型确实覆盖了ToString)。

  

如果C#对象有一个被覆盖的ToString(),调试器将调用覆盖并显示其结果而不是标准{<typeName>}

显然这不适用于枚举。我最好的猜测是调试器正在尝试对枚举类型进行一些特殊的,未记录的处理。添加documentation显然可以通过覆盖此行为来解决问题。

[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一起使用,但您可以使用DebuggerDisplayAttribute,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);
    }
}

答案 1 :(得分:1)

你可以通过一点点工作来完成你的要求。我在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;
}