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

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

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

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

Отвечает ли это на ваш вопрос? Enum "Наследование"

T.Todua 30.10.2019 20:02
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
89
1
88 313
15
Перейти к ответу Данный вопрос помечен как решенный

Ответы 15

Вы не можете наследовать / расширять перечисление, вы можете использовать атрибуты для объявить описание. Если вы ищете целочисленное значение, оно встроено.

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

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

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

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

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

Ответ принят как подходящий

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

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

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

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

На самом деле причина просто в том, что в этом нет никакого смысла. Проблема, о которой вы упомянули, т.е. клиентский код, получающий константу, которую он не ожидает, все еще существует с текущей реализацией - на самом деле компилятор не позволяет вам switch для значений перечисления, не предоставив регистр default или не выбросив исключение. И даже если бы это было так, перечисление во время выполнения могло бы поступить из отдельной компиляции

Raffaele 02.05.2014 14:37

@Raffaele Я только что попробовал это, и, по крайней мере, в Java 7 вы можете включить перечисление без случая default или бросания.

Dathan 06.05.2014 21:38

@Dathan, ты определенно прав! Как написано, это просто неправильно - может, стоит убрать? Я думал о случае, когда вы используете switch для предоставления необходимого значения, такого как инициал локального или return

Raffaele 06.05.2014 22:18

Это не личное, Рик. Но это в значительной степени худшая причина на свете! Полиморфизм и перечисление ... DayOfWeek a = (DayOfWeek) 1; DayOfWeek b = (DayOfWeek) 4711; Console.WriteLine(a + ", " + b);

Bitterblue 15.04.2015 15:16

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

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 / чего-то еще)?

Tom Hawtin - tackline 11.09.2008 16:29

Я так не верю. Или, по крайней мере, не для ситуации, когда вы хотите наследовать от этого класса, чтобы добавить новые значения «перечисления». Окончательное и запечатанное предотвращение наследования IIRC.

alastairs 11.09.2008 23:50

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

crush 13.08.2014 23:09

Например, добавив readonly, ala: public static readonly Action DoIt = new Action("Do it", "This does things");

ErikE 20.07.2015 23:43

Если вы имеете в виду расширение в смысле базового класса, то в 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 13.09.2011 18:50

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

Однако то, что вы можете сделать в Java (и, предположительно, C++ 0x), - это иметь интерфейс вместо класса перечисления. Затем поместите стандартные значения в перечисление, реализующее эту функцию. Очевидно, вы не можете использовать 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
}

Некоторое время назад видел сообщение об этом для Java, посмотрите http://www.javaspecialists.eu/archive/Issue161.html.

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

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

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

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

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

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

интересный анализ. никогда не думал об этом так!

Nerrve 11.11.2012 20:59

Интересно, но я считаю, что это неправильно. Предполагать, что в подклассе должно быть меньше типов, имеет смысл с точки зрения деревьев классификации животных, но не с точки зрения кода. Когда вы создаете подкласс в коде, методов никогда не становится меньше. Переменных-членов меньше никогда не бывает. Ваш аргумент, похоже, неприменим к программному обеспечению, как к классификации животных.

Kieveli 11.03.2013 17:48

@Kieveli, ваш анализ неверен. Добавление члена не оказывает на объект такого же эффекта, как добавление к набору его возможных значений. Это не выдумал Кен; существуют ясные и точные правила информатики, объясняющие, почему это работает именно так. Попробуйте поискать ковариацию и контравариантность (не только академическую мастурбацию, это поможет вам понять правила для таких вещей, как сигнатуры функций и контейнеры).

jwg 27.03.2013 21:04

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

evictednoise 07.09.2016 13:30

Этот пример надуман и не доказывает, что в расширенном классе никогда не должно быть БОЛЬШЕ значений перечисления. enum VehicalParts {Лицензия, Скорость, Емкость}; enum CarParts {SteeringWheel, Winshield}; + все, что есть в Vehical

Bernoulli Lizard 28.02.2020 22:21

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

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

Я хотел бы иметь возможность добавлять значения в перечисления 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;

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

Что касается java, это запрещено, потому что добавление элементов в перечисление фактически создало бы суперкласс, а не подкласс.

Учитывать:

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

Общий вариант использования полиморфизма:

 Person person = Student.ROSS;   //not legal

что явно неверно.

Некоторое время назад я даже хотел сделать что-то подобное и обнаружил, что расширения 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;
   }
}

Временный / локальный обходной путь, когда вам просто нужно очень локальное / одноразовое использование:

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 "Наследование"

Другие вопросы по теме