开发者

Enum bit fields that support inheritance

I apologize for the Title of this Question, but I couldn't think of a better way to describe what I'm trying to accomplish..

I want to make an Enum using the Flags attribute whereby the bitwise values of the enum create somewhat of an inheritance schema.

Example: Say we have a bit field enum of food:

[Flags]
enum Foods
{
    // Top Level foods
    Meat,
    Fruit,
    Vegetables,
    Dairy,
    Grain,
    BenJerrysOatmealCookieChunk, // ;)

    // sub-Meat
    Chicken,
    Beef,
    Fish,

    // sub-Fruit
    Apple,
    Banana,

    // sub-Vegetables
    Spinach,
    Broccoli,

    // sub-Dairy
    Cheese,
    Milk,

    // sub-Grain
    Bread,
    Pasta,
    Rice,

    // sub-Beef
    Tritip,
    Hamburger,

    // sub-Fish
    Salmon,
    Halibut,    
}

Notice how the enum contains sub-categories of food as well as sub-sub-categories. In my real-world scenario, I have an unknown number of top-level categories and an unknown number of sub-level categories (including an unknown level of sub-sub-sub-n-categories)

I would like to set the bit values of the enum such that sub-categories (and sub-sub-n-categories) will "inherit" the bit value of its immediate parent.

I am wanting to avoid the client setter of the Foods property to have to explicitly set all flags for a particular food. In other words, I do not want t开发者_JAVA百科he client setter to have to do something like:

myClass.Foods = Foods.Tritip | Foods.Beef | Foods.Meat;

I am trying to create a construct so that the client setter simply has to set one flag and all parents of that flag are implicit within the child flag.

So if the client setter did this:

myClass.Foods = Foods.Tritip;

We would be able to run the following conditions successfully:

if((myClass.Foods & Foods.Meat) == Foods.Meat)
 Console.WriteLine("I am meat");

if((myClass.Foods & Foods.Beef) == Foods.Beef)
 Console.WriteLine("I am beef");

if((myClass.Foods & Foods.Tritip) == Foods.Tritip)
 Console.WriteLine("I am tritip");

if((myClass.Foods & Foods.Meat) == Foods.Meat)
 Console.WriteLine("I am meat");

if((myClass.Foods & Foods.Rice) != Foods.Rice)
 Console.WriteLine("I am definitely not rice!");

What confuses me about this problem is what bit values to use for each food item in the enum. I initially started out with:

[Flags]
    enum Foods
    {
        // Top Level foods
        Meat = 0x1,
        Fruit = 0x2,
        Vegetables = 0x4,
        Dairy = 0x8,
        Grain = 0x10,
        BenJerrysOatmealCookieChunk = 0x20, // ;)

        ...and so on.
     }

.. but since I don't know how many levels there will be (the enum list will grow over time).. I don't know how to create my bit values so that they allow for growth.

Has anyone attempted to do this before? If so, what was your business logic for your bit values?

I have a sneaky feeling this Question will need a bounty ;)


You could do this by doing bitwise OR inside of the enum declaration itself. While this will not give you a nice clean ToString like you normally get with the Flags attribute, it does give you the desired result:

[Flags]
public enum Foods : ulong
{
    Meat = 0x100000,
    Fruit = 0x200000,
    Vegetables = 0x400000,
    Dairy = 0x800000,
    Grain = 0xF00000,

    Beef = Meat | 0x100,
    Chicken = Meat | 0x200,
    Fish = Meat | 0x400,

    Apple = Fruit | 0x100,
    Banana = Fruit | 0x200,

    Spinach = Vegetables | 0x100,
    Broccoli = Vegetables | 0x200,

    Cheese = Dairy | 0x100,
    Milk = Dairy | 0x200,

    Bread = Grain | 0x100,
    Pasta = Grain | 0x200,
    Rice = Grain | 0x400,

    Tritip = Beef | 0x1,
    Hamburger = Beef | 0x2,

    Salmon = Fish | 0x100,
    Halibut = Fish | 0x200,  
}

I used ulong for the enum's base so that you get more space for subcategories. I didn't start at the highest order bit possible, but if I were you, I would so that there would be room to represent deeper categories. Also, you'd want to play around with the spacing between sub-categories to yield the optimal results.


OP said (and I quote)

Notice how the enum contains sub-categories of food as well as sub-sub-categories. In my real-world scenario, I have an unknown number of top-level categories and an unknown number of sub-level categories (including an unknown level of sub-sub-sub-n-categories).

Then you don't have an enum: you have some sort of type hierarchy. Enums (by design) wrap some sort of integral type and have a fixed number of elements. The fact that they are fundamentally fixed-point integers puts an upper bound on the number of available bits: an enum is 8-, 16-, 32- or 64-bits in size.

Given those restriction enums, you can do something like the following (and you don't need or want the [Flags] attribute). In the example below, I've reserved 32 bits for the "category" and 32 bits for the "subcategory".

enum Foods : ulong
{
  CategoryMask                = 0xFFFFFFFF00000000 ,


  // Top Level foods
  Meat                        = 0x0000000100000000 ,
  Fruit                       = 0x0000000200000000 ,
  Vegetables                  = 0x0000000400000000 ,
  Dairy                       = 0x0000000800000000 ,
  Grain                       = 0x0000001000000000 ,
  BenJerrysOatmealCookieChunk = 0x0000002000000000 , // ;)

  Chicken   = Meat            | 0x0000000000000001 ,
  Beef      = Meat            | 0x0000000000000002 ,
  Fish      = Meat            | 0x0000000000000004 ,
  Apple     = Fruit           | 0x0000000000000001 ,
  Banana    = Fruit           | 0x0000000000000002 ,
  Spinach   = Vegetables      | 0x0000000000000001 ,
  Broccoli  = Vegetables      | 0x0000000000000002 ,
  Cheese    = Dairy           | 0x0000000000000001 ,
  Milk      = Dairy           | 0x0000000000000002 ,
  Bread     = Grain           | 0x0000000000000001 ,
  Pasta     = Grain           | 0x0000000000000002 ,
  Rice      = Grain           | 0x0000000000000004 ,
  TriTip    = Beef            | 0x0000000000000001 ,
  Hamburger = Beef            | 0x0000000000000002 ,
  Salmon    = Fish            | 0x0000000000000004 ,
  Halibut,  = Fish            | 0x0000000000000008 ,
}

Note that interrogating a Foods value for its category will requires some bit twiddling, like so:

Foods item     = Foods.Hamburger ;
Foods category = (Foods) ( (ulong)item & (ulong)Foods.CategoryMask ) ;


This is not possible. Enums cannot inherit from other enums. In fact all enums must actually inherit from System.Enum. C# allows syntax to change the underlying representation of the enum values which looks like inheritance, but in actuality they still inherit from System.enum.

See section 8.5.2 of the CLI spec for the full details. Relevant information from the spec

  • All enums must derive from System.Enum
  • Because of the above, all enums are value types and hence sealed

You could, however, achieve what you want with a base class and derived classes.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜