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

Alastairs
fonte
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

Rik
fonte
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.

Ken
fonte
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.

tsimon
fonte
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
}
Tom Hawtin - tackline
fonte
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".

McKenzieG1
fonte
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

Oskar
fonte
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;
    }        
}
Allain Lalonde
fonte
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?

Darrell Denlinger
fonte
0

Você não pode herdar / estender um enum, você pode usar atributos para declarar uma descrição . Se você está procurando um valor inteiro, ele está embutido.

bdukes
fonte
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.

Chris Roberts
fonte
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.

Magnum
fonte
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;
   }
}
Sultão
fonte
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.

Aniket Thakur
fonte
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"

T.Todua
fonte