Розширення переліку шляхом успадкування


85

Я знаю, що це суперечить ідеї перелічень, але чи можна розширити перерахування в C # / Java? Я маю на увазі "розширити" як у сенсі додавання нових значень до переліку, так і в сенсі ОО успадкування від існуючого переліку.

Я припускаю, що це неможливо на Java, оскільки вона отримала їх досить недавно (Java 5?). Однак C # здається більш простим до людей, які хочуть робити божевільні речі, тому я подумав, що це можливо якось. Імовірно, його можна зламати за допомогою роздумів (не те, що ви насправді використовували б цей метод)?

Я не обов'язково зацікавлений у застосуванні будь-якого даного методу, це просто викликало мою цікавість, коли мені це спало на думку :-)


Це відповідає на ваше запитання? Enum "Спадщина"
Т.Тодуа

Відповіді:


103

Причина, по якій ви не можете продовжити перерахування, полягає в тому, що це призведе до проблем з поліморфізмом.

Скажімо, у вас є перелік MyEnum зі значеннями A, B і C, і розширіть його зі значенням D як MyExtEnum.

Припустимо, метод очікує десь значення myEnum, наприклад як параметр. Повинно бути законним подавати значення MyExtEnum, оскільки це підтип, але що тепер ви збираєтесь робити, коли виявляється, що значення D?

Щоб усунути цю проблему, продовження переліків заборонено


4
Насправді, причина просто в тому, що це не має жодного сенсу. Проблема, про яку ви згадали, тобто код клієнта, який отримує константу, якої він не очікує, все ще існує з поточною реалізацією - насправді компілятор не дозволяє вам switchперераховувати значення, не надаючи defaultвипадок або не створюючи виняток. І навіть якби це сталося, перерахування під час виконання могло б виходити з окремої компіляції
Раффаеле,

@Raffaele Я щойно спробував це, і принаймні в Java 7 ви можете ввімкнути перерахування без defaultсправи та кидання.
Dathan

@Dathan, ти точно маєш рацію! Як написано, це просто неправильно - можливо, мені слід його видалити? Я думав про випадок, коли ви використовуєте a, switchщоб надати необхідне значення, наприклад, ініціал локального, абоreturn
Raffaele

3
Це не особисто, Рік. Але це майже найгірша причина коли-небудь! Поліморфізм і перерахування ...DayOfWeek a = (DayOfWeek) 1; DayOfWeek b = (DayOfWeek) 4711; Console.WriteLine(a + ", " + b);
Bitterblue

41

Коли вбудованих переліків недостатньо, ви можете зробити це за старою модою і створити свій власний. Наприклад, якщо ви хочете додати додаткову властивість, наприклад, поле опису, ви можете зробити це наступним чином:

public class Action {
    public string Name {get; private set;}
    public string Description {get; private set;}

    private Action(string name, string description) {
        Name = name;
        Description = description;
    }

    public static Action DoIt = new Action("Do it", "This does things");
    public static Action StopIt = new Action("Stop It", "This stops things");
}

Потім ви можете розглядати це як перерахування так:

public void ProcessAction(Action a) {
    Console.WriteLine("Performing action: " + a.Name)
    if (a == Action.DoIt) {
       // ... and so on
    }
}

Фокус полягає в тому, щоб переконатися, що конструктор є приватним (або захищеним, якщо ви хочете успадкувати), і що ваші екземпляри статичні.


Я не людина C #, але чи не хочете ви там трохи остаточного (або запечатаного / const / чого завгодно)?
Том Хоутін - таклін

1
Я не вірю. Або, принаймні, не для ситуації, коли ви хочете успадкувати від цього класу, щоб додати нові значення "перерахування". Остаточне і скріплене запобігання спадкування IIRC.
alastairs

4
@alastairs Я думаю, він мав на увазі додавання finalдо public static Action DoIt = new Action("Do it", "This does things");рядка, а не класу.
розчавити

1
Як у додаванні readonly, ала:public static readonly Action DoIt = new Action("Do it", "This does things");
ErikE

40

Ви йдете неправильним шляхом: підклас переліку матиме менше записів.

У псевдокоді подумайте:

enum Animal { Mosquito, Dog, Cat };
enum Mammal : Animal { Dog, Cat };  // (not valid C#)

Будь-який метод, який може прийняти тварину, повинен мати змогу прийняти ссавців, але не навпаки. Підкласифікація призначена для створення чогось більш конкретного, а не більш загального. Ось чому "об'єкт" є коренем ієрархії класів. Так само, якби перелічення були спадкоємними, то гіпотетичний корінь ієрархії перерахування мав би всі можливі символи.

Ні, C # / Java не дозволяють перечислення, AFAICT, хоча часом це було б дуже корисно. Можливо, тому, що вони вирішили застосувати Enums як ints (як C) замість інтернованих символів (як Lisp). (Вище, що представляє (Тварина) 1, а що (Ссавці) 1, і чи однакові вони?)

Однак ви можете написати власний клас, подібний до переліку (з іншою назвою), який надав це. З атрибутами C # це може виглядати навіть гарно.


3
цікавий аналіз. ніколи не думав про це так!
Nerrve

Цікаво, але я вважаю, що це неправильно. Мається на увазі, що в підкласі повинно бути менше типів з точки зору дерев класифікації тварин, але не з точки зору коду. Коли ви виконуєте підкаска в коді, методів ніколи не стає менше. Ніколи не стає менше змінних членів. Схоже, ваш аргумент не стосується програмного забезпечення, як і класифікацій тварин.
Kieveli

4
@Kieveli, ваш аналіз помилковий. Додавання члена не має такого самого ефекту на об’єкт, як додавання до набору можливих значень. Це не те, що Кен вигадував; існують чіткі та точні правила інформатики, які пояснюють, чому вона працює саме так. Спробуйте шукати коваріацію та контраваріацію (не лише академічну мастурбацію, це допоможе вам зрозуміти правила для таких речей, як підписи функцій та контейнери).
jwg

@Kieveli Припустимо, у вас був клас 'StreamWriter' (бере потік - пише потік). Ви створили б "TextWriter: StreamWriter" (бере потік - пише текст). Тепер ви створюєте "HtmlWriter: TextWriter" (бере потік - пише досить html). Тепер HtmlWriter, очевидно, має більше членів, методів тощо. Тепер спробуйте взяти потік зі звукової карти і записати його у файл за допомогою HtmlWriter :)
evictednoise

Цей приклад надуманий і не доводить, що в розширеному класі ніколи не повинно бути БІЛЬШЕ значень переліку. enum VehicalParts {Ліцензія, швидкість, ємність}; перерахувати CarParts {SteeringWheel, Winshield}; + все, що є у Vehical
Бернуллі Ящірка

12

Перерахування повинні представляти перерахування всіх можливих значень, тому розширення скоріше суперечить ідеї.

Однак те, що ви можете зробити в Java (і, мабуть, C ++ 0x), - це мати інтерфейс замість класу enum. Потім помістіть стандартні значення в перелік, який реалізує функцію. Очевидно, що ви не можете використовувати java.util.EnumSet тощо. Це підхід, використаний у "більшій кількості функцій NIO", який повинен бути в JDK7.

public interface Result {
    String name();
    String toString();
}
public enum StandardResults implements Result {
    TRUE, FALSE
}


public enum WTFResults implements Result {
    FILE_NOT_FOUND
}

4

Ви можете використовувати відображення .NET для отримання міток та значень із існуючого переліку під час виконання ( Enum.GetNames()і Enum.GetValues()це два конкретні методи, які ви б використовували), а потім використовувати ін’єкцію коду, щоб створити нову з цими елементами та деякими новими. Це здається дещо аналогічним "успадкуванню від існуючого переліку".


2

Додавання перерахувань є досить поширеною справою, якщо ви повертаєтесь до вихідного коду та редагуєте, будь-який інший спосіб (успадкування або відображення, якщо це можливо), швидше за все, повернеться і вдарить вас, коли ви оновите бібліотеку та вони ввели одне й те саме ім'я перечислення або одне і те ж значення переліку - я бачив безліч коду низького рівня, де ціле число збігається з двійковим кодуванням, де ви могли б зіткнутися з проблемами

В ідеалі коди, що посилаються на код, слід писати лише як дорівнює (або комутатори), і намагатися бути доказом у майбутньому, не очікуючи, що набір перечислень є const


2

Якщо ви маєте на увазі розширення в розумінні базового класу, то в Java ... ні.

Але ви можете розширити значення переліку, щоб мати властивості та методи, якщо це саме те, що ви маєте на увазі.

Наприклад, наступне використовує перерахування дужок:

class Person {
    enum Bracket {
        Low(0, 12000),
        Middle(12000, 60000),
        Upper(60000, 100000);

        private final int low;
        private final int high;
        Brackets(int low, int high) {
            this.low = low;
            this.high = high;
        }

        public int getLow() {
            return low;
        }

        public int getHigh() {
            return high;
        }

        public boolean isWithin(int value) {
           return value >= low && value <= high;
        }

        public String toString() {
            return "Bracket " + low + " to " + high;
        }
    }

    private Bracket bracket;
    private String name;

    public Person(String name, Bracket bracket) {
        this.bracket = bracket;
        this.name = name;
    }

    public String toString() {
        return name + " in " + bracket;
    }        
}

Це звучить як тип значення (struct) у .NET. Я не знав, що ти можеш це робити на Java.
alastairs

2

Я не бачив, щоб хтось інший згадував про це, але порядкове значення переліку важливе. Наприклад, для граалів, коли ви зберігаєте перелік у базі даних, вона використовує порядкове значення. Якби ви могли якось розширити перелік, якими були б порядкові значення ваших розширень? Якби ви поширили його в кількох місцях, як би ви могли зберегти якийсь порядок для цих ординалів? Хаос / нестабільність порядкових значень було б поганою річчю, що, мабуть, є ще однією причиною, чому дизайнери мов не торкались цього.

Ще одна складність, якщо ви були мовним дизайнером, як можна зберегти функціональність методу values ​​(), який повинен повертати всі значення переліку. На що б ви закликали це і як би воно зібрало всі цінності?




0

Хм-м-м - наскільки мені відомо, цього зробити не можна - переліки записуються під час проектування і використовуються як зручність для програміста.

Я майже впевнений, що під час компіляції коду еквівалентні значення будуть замінені на імена у вашому переліку, тим самим усунувши поняття переліку та (отже) можливість його розширення.


0

Я хотів би мати можливість додавати значення до перерахувань C #, які є комбінаціями існуючих значень. Наприклад (це те, що я хочу зробити):

AnchorStyles визначається як

public enum AnchorStyles { None = 0, Top = 1, Bottom = 2, Left = 4, Right = 8, }

і я хотів би додати AnchorStyles.BottomRight = Вправо + Внизу, замість того, щоб говорити

my_ctrl.Anchor = AnchorStyles.Right | AnchorStyles.Bottom;

Я можу просто сказати

my_ctrl.Anchor = AnchorStyles.BottomRight;

Це не спричиняє жодної з вищезазначених проблем, тому було б непогано, якби це було можливо.


0

Деякий час тому я навіть хотів зробити щось подібне і виявив, що розширення переліку перетворюють багато основних концепцій ...

Але все-таки вам, можливо, доведеться зробити, якщо перелічення оголошено у зовнішній бібліотеці, і пам'ятайте, що ви повинні бути особливо обережними при використанні цього розширення переліку ...

public enum MyEnum { A = 1, B = 2, C = 4 }

public const MyEnum D = (MyEnum)(8);
public const MyEnum E = (MyEnum)(16);

func1{
    MyEnum EnumValue = D;

    switch (EnumValue){
      case D:  break;
      case E:  break;
      case MyEnum.A:  break;
      case MyEnum.B:  break;
   }
}

0

Що стосується Java, це заборонено, оскільки додавання елементів до переліку ефективно створить супер-клас, а не підклас.

Розглянемо:

 enum Person (JOHN SAM}   
 enum Student extends Person {HARVEY ROSS}

Це був би загальний випадок поліморфізму

 Person person = Student.ROSS;   //not legal

що явно неправильно.


0

Тимчасовий / локальний обхідний шлях , коли ви просто хочете дуже локальне / одноразове використання:

enum Animals { Dog, Cat }
enum AnimalsExt { Dog = Animals.Dog, Cat= Animals.Cat,  MyOther}
// BUT CAST THEM when using:
var xyz = AnimalsExt.Cat;
MethodThatNeedsAnimal(   (Animals)xyz   );

Переглянути всі відповіді на: Enum "Спадщина"

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.