Расширение перечисления через наследование

88

Я знаю, что это скорее противоречит идее перечислений, но можно ли расширить перечисления в C # / Java? Я имею в виду «расширить» как в смысле добавления новых значений в перечисление, так и в объектно-ориентированном смысле наследования от существующего перечисления.

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

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

Аластерс
источник
Отвечает ли это на ваш вопрос? Enum "Inheritance"
T.Todua

Ответы:

105

Причина, по которой вы не можете расширить Enums, заключается в том, что это приведет к проблемам с полиморфизмом.

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

Предположим, метод ожидает где-то значение myEnum, например, в качестве параметра. Предоставление значения MyExtEnum должно быть законным, потому что это подтип, но что теперь вы собираетесь делать, когда выясняется, что значение равно D?

Чтобы устранить эту проблему, расширение перечислений запрещено

Рик
источник
4
На самом деле причина просто в том, что в этом нет никакого смысла. Проблема, о которой вы упомянули, т.е. клиентский код, получающий константу, которую он не ожидает, все еще существует с текущей реализацией - на самом деле компилятор не позволяет вам использовать switchзначения перечисления без указания defaultслучая или исключения исключения. И даже если бы это было так, перечисление во время выполнения могло бы поступать из отдельной компиляции
Раффаэле
@Raffaele Я только что попробовал это, и, по крайней мере, в Java 7 вы можете включить перечисление без defaultслучая или бросания.
Дафан
@ Датан, ты определенно прав! Как написано, это просто неправильно - может, стоит убрать? Я думал о том случае , когда вы используете , switchчтобы обеспечить требуемое значение, например, инициал местных, илиreturn
Раффаэле
3
Это не личное, Рик. Но это самая худшая причина на свете! Полиморфизм и перечисление ...DayOfWeek a = (DayOfWeek) 1; DayOfWeek b = (DayOfWeek) 4711; Console.WriteLine(a + ", " + b);
Bitterblue
41

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

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

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

Любой метод, который может принимать Животное, должен уметь принимать Млекопитающее, но не наоборот. Создание подклассов предназначено для создания чего-то более конкретного, а не более общего. Вот почему «объект» является корнем иерархии классов. Точно так же, если бы перечисления были наследуемыми, то гипотетический корень иерархии перечислений имел бы все возможные символы.

Но нет, C # / Java не допускают вложенных перечислений AFAICT, хотя иногда это было бы действительно полезно. Вероятно, это потому, что они решили реализовать Enums как int (например, C) вместо интернированных символов (например, Lisp). (Выше, что представляет (Животное) 1 и что представляет (Млекопитающее) 1, и имеют ли они одно и то же значение?)

Однако вы могли бы написать свой собственный enum-подобный класс (с другим именем), который это обеспечил бы. С атрибутами C # это могло бы даже выглядеть неплохо.

Кен
источник
3
интересный анализ. никогда не думал об этом так!
Nerrve
Интересно, но я считаю, что это неправильно. Предположение, что в подклассе должно быть меньше типов, имеет смысл с точки зрения деревьев классификации животных, но не с точки зрения кода. Когда вы подклассифицируете код, методов никогда не становится меньше. Переменных-членов меньше никогда не бывает. Ваш аргумент, похоже, не применим к программному обеспечению в отличие от классификации животных.
Киевели
4
@Kieveli, ваш анализ неверен. Добавление члена не оказывает на объект такого же эффекта, как добавление к набору его возможных значений. Это не выдумал Кен; существуют ясные и точные правила информатики, объясняющие, почему это работает именно так. Попробуйте поискать ковариацию и контравариантность (не только академическую мастурбацию, это поможет вам понять правила для таких вещей, как сигнатуры функций и контейнеры).
jwg
@Kieveli Предположим, у вас есть класс StreamWriter (принимает поток - записывает поток). Вы бы создали TextWriter: StreamWriter (берет поток - записывает текст). Теперь вы создаете «HtmlWriter: TextWriter» (берет поток - пишет красивый html). Теперь у HtmlWriter явно больше членов, методов и так далее. Теперь попробуйте взять поток со звуковой карты и записать его в файл с помощью HtmlWriter :)
evicednoise
Этот пример надуман и не доказывает, что в расширенном классе никогда не должно быть БОЛЬШЕ значений перечисления. enum VehicalParts {Лицензия, Скорость, Емкость}; enum CarParts {SteeringWheel, Winshield}; + все, что есть у Vehical,
Bernoulli Lizard
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 #, но разве вы не хотите, чтобы там было немного final (или запечатанного / const / чего-то еще)?
Том Хотин - tackline
1
Я так не верю. Или, по крайней мере, не для ситуации, когда вы хотите наследовать от этого класса, чтобы добавить новые значения «перечисления». Окончательное и запечатанное предотвращает наследование IIRC.
alastairs,
4
@alastairs Я думаю, он имел в виду добавление finalк public static Action DoIt = new Action("Do it", "This does things");строке, а не к классу.
Crush
1
readonlypublic static readonly Action DoIt = new Action("Do it", "This does things");
То есть,
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
}
Том Хотин - tackline
источник
4

Вы можете использовать отражение .NET для извлечения меток и значений из существующего перечисления во время выполнения ( Enum.GetNames()и Enum.GetValues()это два конкретных метода, которые вы могли бы использовать), а затем использовать внедрение кода для создания нового с этими элементами и некоторыми новыми. Это несколько похоже на «наследование от существующего перечисления».

McKenzieG1
источник
2

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

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

Оскар
источник
2

Если вы имеете в виду расширение в смысле базового класса, то в Java ... нет.

Но вы можете расширить значение перечисления, чтобы иметь свойства и методы, если вы это имеете в виду.

Например, в следующем примере используется перечисление Bracket:

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;
    }        
}
Аллен Лалонд
источник
Это звучит как тип значения (структура) в .NET. Я не знал, что вы можете сделать это на Java.
alastairs
2

Я не видел, чтобы кто-то еще упоминал об этом, но порядковый номер перечисления важен. Например, с grails, когда вы сохраняете перечисление в базе данных, оно использует порядковое значение. Если бы вы могли каким-то образом расширить перечисление, каковы были бы порядковые значения ваших расширений? Если вы расширили его в нескольких местах, как бы вы могли сохранить какой-то порядок в этих порядковых номерах? Хаос / нестабильность в порядковых значениях - это плохо, что, вероятно, является еще одной причиной, по которой разработчики языка не коснулись этого.

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

Даррелл Денлингер
источник
0

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

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

Крис Робертс
источник
0

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

AnchorStyles определяется как

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

и я хотел бы добавить AnchorStyles.BottomRight = Right + Bottom, чтобы вместо того, чтобы говорить

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

Я могу просто сказать

my_ctrl.Anchor = AnchorStyles.BottomRight;

Это не вызывает никаких проблем, которые были упомянуты выше, поэтому было бы неплохо, если бы это было возможно.

Магнум
источник
0

Некоторое время назад я даже хотел сделать что-то подобное и обнаружил, что расширения enum аннулируют множество базовых концепций ... (Не только полиморфизм)

Но все же вам может потребоваться это сделать, если перечисление объявлено во внешней библиотеке, и помните, что вы должны проявлять особую осторожность при использовании этих расширений перечисления ...

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

Т. Тодуа
источник