Estendendo um enum por meio de herança


88

Eu sei que isso vai contra a ideia de enums, mas é possível estender enums em C # / Java? Quero dizer "estender" no sentido de adicionar novos valores a um enum, mas também no sentido OO de herdar de um enum existente.

Suponho que não seja possível em Java, já que só os obteve recentemente (Java 5?). C # parece perdoar mais as pessoas que querem fazer coisas malucas, então eu pensei que poderia ser possível de alguma forma. Presumivelmente, ele poderia ser hackeado por meio de reflexão (não que você realmente usasse esse método).

Não estou necessariamente interessado em implementar qualquer método, apenas me despertou curiosidade quando me ocorreu :-)


Isso responde sua pergunta? Enum "Herança"
T.Todua

Respostas:


105

A razão pela qual você não pode estender Enums é porque isso levaria a problemas com polimorfismo.

Digamos que você tenha um enum MyEnum com os valores A, B e C e o estenda com o valor D como MyExtEnum.

Suponha que um método espera um valor myEnum em algum lugar, por exemplo, como um parâmetro. Deve ser legal fornecer um valor MyExtEnum, porque é um subtipo, mas agora o que você vai fazer quando descobrir que o valor é D?

Para eliminar esse problema, estender enums é ilegal


4
Na verdade, a razão é simplesmente que não faz sentido. O problema que você mencionou, ou seja, o código do cliente recebendo uma constante que não esperava, ainda existe com a implementação atual - na verdade, o compilador não permite switchvalores enum sem fornecer um defaultcaso ou lançar uma exceção. E, mesmo se viesse, o enum em tempo de execução poderia vir de uma compilação separada
Raffaele

@Raffaele Acabei de tentar isso e, pelo menos no Java 7, você pode ativar um enum sem defaultcase ou jogando.
Dathan

@Dathan você está definitivamente certo! Como está escrito, isso está simplesmente errado - talvez eu deva removê-lo? Eu estava pensando no caso quando você usa um switchpara fornecer um valor obrigatório, como a inicial de um local, ou parareturn
Raffaele

3
Isso não é pessoal, Rik. Mas esse é o pior motivo de todos! Polimorfismo e enum ...DayOfWeek a = (DayOfWeek) 1; DayOfWeek b = (DayOfWeek) 4711; Console.WriteLine(a + ", " + b);
Bitterblue

41

Você está indo na direção errada: uma subclasse de um enum teria menos entradas.

Em pseudocódigo, pense:

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

Qualquer método que pode aceitar um Animal deve ser capaz de aceitar um Mamífero, mas não o contrário. A criação de subclasses é para tornar algo mais específico, não mais geral. É por isso que "objeto" é a raiz da hierarquia de classes. Da mesma forma, se enums fossem herdáveis, uma raiz hipotética da hierarquia de enum teria todos os símbolos possíveis.

Mas não, C # / Java não permite sub-enums, AFAICT, embora seja muito útil às vezes. Provavelmente é porque eles escolheram implementar Enums como ints (como C) em vez de símbolos internos (como Lisp). (Acima, o que (Animal) 1 representa e o que (Mamífero) 1 representa, e eles têm o mesmo valor?)

Você poderia escrever sua própria classe enum (com um nome diferente) que fornecesse isso, no entanto. Com atributos C #, pode até parecer legal.


3
análise interessante. nunca pensei nisso dessa forma!
Nerrve

Interstante, mas acho que está errado. Implicar que deveria haver menos tipos em uma subclasse faz sentido em termos de árvores de classificação de animais, mas não em termos de código. Quando você subcassa em código, nunca há menos métodos. Nunca há menos variáveis ​​de membro. Seu argumento não parece se aplicar ao software como se aplica às classificações de animais.
Kieveli

4
@Kieveli, sua análise está errada. Adicionar um membro não tem o mesmo efeito em um objeto que adicioná-lo ao conjunto de seus valores possíveis. Isso não é algo que Ken inventou; existem regras claras e precisas da ciência da computação que explicam por que funciona dessa maneira. Tente pesquisar por covariância e contravariância (não apenas masturbação acadêmica, ela o ajudará a entender regras para coisas como assinaturas de funções e recipientes).
jwg

@Kieveli Suponha que você tenha uma classe 'StreamWriter' (pega stream - escreve stream). Você criaria 'TextWriter: StreamWriter' (pega stream - escreve texto). Agora você cria 'HtmlWriter: TextWriter' (pega stream - escreve bastante html). Agora, o HtmlWriter obviamente tem mais membros, métodos etc. Agora tente pegar um stream de uma placa de som e gravá-lo em um arquivo usando HtmlWriter :)
evictednoise

Este exemplo é inventado e não prova que nunca deve haver MAIS valores enum na classe estendida. enum VehicalParts {Licença, Velocidade, Capacidade}; enum CarParts {SteeringWheel, Winshield}; + tudo que Vehical também tem
Bernoulli Lizard

41

Quando enums integrados não são suficientes, você pode fazer à moda antiga e criar o seu próprio. Por exemplo, se você quiser adicionar uma propriedade adicional, por exemplo, um campo de descrição, você pode fazer da seguinte maneira:

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

Você pode então tratá-lo como um enum, assim:

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

O truque é ter certeza de que o construtor é privado (ou protegido se você quiser herdar) e que suas instâncias são estáticas.


Eu não sou uma pessoa C #, mas você não quer um pouco de final (ou selado / const / qualquer coisa) aí?
Tom Hawtin - tackline

1
Eu não acredito nisso. Ou, pelo menos, não para a situação em que você deseja herdar desta classe para adicionar novos valores "enum". Final e selado prevenir herança IIRC.
Alastairs

4
@alastairs Eu acho que ele quis dizer adicionar finalà public static Action DoIt = new Action("Do it", "This does things");linha, ao invés da classe.
esmagamento de

1
Acrescentando readonly, ala:public static readonly Action DoIt = new Action("Do it", "This does things");
ErikE

12

Supõe-se que os enums representam a enumeração de todos os valores possíveis, portanto, estender, em vez disso, vai contra a ideia.

No entanto, o que você pode fazer em Java (e provavelmente C ++ 0x) é ter uma interface em vez de uma classe enum. Em seguida, coloque os valores padrão em um enum que implementa o recurso. Obviamente, você não pode usar java.util.EnumSet e similares. Esta é a abordagem adotada em "mais recursos NIO", que deveria estar no JDK7.

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


public enum WTFResults implements Result {
    FILE_NOT_FOUND
}

4

Você pode usar a reflexão do .NET para recuperar os rótulos e valores de um enum existente em tempo de execução ( Enum.GetNames()e Enum.GetValues()são os dois métodos específicos que você usaria) e, em seguida, usar injeção de código para criar um novo com esses elementos mais alguns novos. Isso parece um pouco análogo a "herdar de um enum existente".


2

Adicionar enums é uma coisa bastante comum de se fazer se você voltar ao código-fonte e editar, qualquer outra forma (herança ou reflexão, se for possível) provavelmente voltará e baterá em você quando você fizer uma atualização da biblioteca e eles introduziram o mesmo nome de enum ou o mesmo valor de enum - tenho visto muitos códigos de baixo nível onde o número inteiro corresponde à codificação binária, onde você teria problemas

O ideal é que enums de referência de código devem ser escritos apenas como iguais (ou opções) e tente ser à prova de futuro, não esperando que o conjunto de enum seja const


2

Se você quer dizer extends no sentido de classe Base, então em Java ... não.

Mas você pode estender um valor enum para ter propriedades e métodos, se é isso que você quer dizer.

Por exemplo, o seguinte usa um enum 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;
    }        
}

Isso soa como um tipo de valor (struct) em .NET. Eu não sabia que você poderia fazer isso em Java.
Alastairs

2

Não vi ninguém mencionar isso, mas o valor ordinal de um enum é importante. Por exemplo, com grails, quando você salva um enum no banco de dados, ele usa o valor ordinal. Se você pudesse de alguma forma estender um enum, quais seriam os valores ordinais de suas extensões? Se você o estendesse em vários lugares, como poderia preservar algum tipo de ordem para esses ordinais? Caos / instabilidade nos valores ordinais seria uma coisa ruim, o que provavelmente é outra razão pela qual os designers da linguagem não tocaram nisso.

Outra dificuldade, se você fosse o designer da linguagem, seria como preservar a funcionalidade do método values ​​() que deve retornar todos os valores enum. Em que você invocaria isso e como reuniria todos os valores?




0

Hmmm - pelo que eu sei, isso não pode ser feito - as enumerações são escritas em tempo de design e são usadas como uma conveniência para o programador.

Tenho certeza de que, quando o código for compilado, os valores equivalentes serão substituídos pelos nomes em sua enumeração, removendo assim o conceito de uma enumeração e (portanto) a capacidade de estendê-la.


0

Eu gostaria de poder adicionar valores às enumerações C # que são combinações de valores existentes. Por exemplo (isso é o que eu quero fazer):

AnchorStyles é definido como

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

e eu gostaria de adicionar AnchorStyles.BottomRight = Right + Bottom, em vez de dizer

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

Eu posso apenas dizer

my_ctrl.Anchor = AnchorStyles.BottomRight;

Isso não causa nenhum dos problemas mencionados acima, então seria bom se fosse possível.


0

Algum tempo atrás, até eu queria fazer algo assim e descobri que as extensões enum anulariam muitos conceitos básicos ... (não apenas polimorfise)

Mas você ainda pode precisar fazer se o enum for declarado em uma biblioteca externa e lembre-se de que você deve ter um cuidado especial ao usar essas extensões de 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

No que diz respeito ao java, não é permitido porque adicionar elementos a um enum criaria efetivamente uma superclasse em vez de uma subclasse.

Considerar:

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

Um caso de uso geral de polimorfismo seria

 Person person = Student.ROSS;   //not legal

o que está claramente errado.


0

Uma solução temporária / local , quando você deseja apenas um uso muito local / único:

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

Veja todas as respostas em: Enum "Herança"

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.