Перечислите битовые поля, которые поддерживают наследование - PullRequest
3 голосов
/ 04 августа 2011

Я прошу прощения за заголовок этого вопроса, но я не мог придумать лучшего способа описать то, что я пытаюсь достичь ..

Я хочу создать Enum с использованием атрибута Flags, чтобы побитовые значения enum создавали некоторую схему наследования.

Пример: скажем, у нас есть битовое поле 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,    
}

Обратите внимание, что перечисление содержит подкатегории продуктов питания, а также подкатегории. В моем реальном сценарии у меня есть неизвестное количество категорий верхнего уровня и неизвестное количество категорий подуровня (включая неизвестный уровень под-под-под-под-n-категорий)

Я бы хотел установить значения битов перечисления так, чтобы подкатегории (и под-под-подкатегории) «наследовали» значение бита своего непосредственного родителя.

Я хочу, чтобы клиентский установщик свойства Foods не устанавливал явно все флаги для определенного продукта. Другими словами, я не хочу, чтобы клиентский установщик делал что-то вроде:

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

Я пытаюсь создать конструкцию, чтобы установщик клиента просто устанавливал один флаг, и все родители этого флага были неявными в дочернем флаге.

Итак, если установщик клиента сделал это:

myClass.Foods = Foods.Tritip;

Мы сможем успешно выполнить следующие условия:

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

Что меня смущает в этой проблеме, так это то, какие битовые значения используются для каждого элемента питания в перечислении. Я изначально начал с:

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

        ...and so on.
     }

.. но так как я не знаю, сколько будет уровней (список enum будет расти со временем) .. Я не знаю, как создавать свои битовые значения, чтобы они учитывали рост.

Кто-нибудь пытался сделать это раньше? Если да, какова была ваша бизнес-логика для ваших битовых значений?

У меня подлое чувство, что этот Вопрос будет нуждаться в награде;)

Ответы [ 3 ]

6 голосов
/ 04 августа 2011

Вы можете сделать это, выполнив побитовое ИЛИ внутри самого объявления enum. Хотя это не даст вам хороший чистый ToString, как вы обычно получаете с атрибутом Flags, он дает вам желаемый результат:

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

Я использовал ulong для базы enum, чтобы вы получили больше места для подкатегорий. Я не начинал с самого высокого возможного бита, но на вашем месте у меня было бы место для представления более глубоких категорий. Кроме того, вы хотите поэкспериментировать с интервалом между подкатегориями, чтобы получить оптимальные результаты.

3 голосов
/ 04 августа 2011

ОП сказал (и я цитирую)

Обратите внимание, что перечисление содержит подкатегории продуктов питания, а также подкатегории.В моем реальном сценарии у меня есть неизвестное количество категорий верхнего уровня и неизвестное количество категорий подуровня (включая неизвестный уровень под-под-под-под-n-категорий).

Тогда у вас нет перечисления: у вас есть какая-то иерархия типов.Перечисления (по замыслу) обертывают некий целочисленный тип и имеют фиксированное количество элементов.Тот факт, что они являются целыми числами с фиксированной запятой, устанавливает верхнюю границу для числа доступных битов: перечисление имеет размер 8, 16, 32 или 64 бита.

Учитывая эти ограничивающие перечисления,Вы можете сделать что-то вроде следующего (и вам не нужен или не нужен атрибут [Flags]).В приведенном ниже примере я зарезервировал 32 бита для «категории» и 32 бита для «подкатегории».

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

Обратите внимание, что запрос значения Foods для его категории потребует некоторого изменения битоввот так:

Foods item     = Foods.Hamburger ;
Foods category = (Foods) ( (ulong)item & (ulong)Foods.CategoryMask ) ;
1 голос
/ 04 августа 2011

Это невозможно.Перечисления не могут наследоваться от других перечислений.Фактически все перечисления должны наследоваться от System.Enum.C # позволяет синтаксису изменять базовое представление значений перечисления, которое выглядит как наследование, но в действительности они все еще наследуются от System.enum.

См. Подробности в разделе 8.5.2 спецификации CLI.Соответствующая информация из спецификации

  • Все перечисления должны быть производными от System.Enum
  • . Из-за вышеизложенного все перечисления являются типами значений и поэтому запечатаны

Однако вы можете достичь желаемого с помощью базового класса и производных классов.

...