Extendiendo una enumeración a través de herencia

85

Sé que esto va en contra de la idea de enumeraciones, pero ¿es posible extender las enumeraciones en C # / Java? Me refiero a "extender" en el sentido de agregar nuevos valores a una enumeración, pero también en el sentido de OO de heredar de una enumeración existente.

Supongo que no es posible en Java, ya que solo los obtuvo recientemente (¿Java 5?). Sin embargo, C # parece más indulgente con las personas que quieren hacer locuras, así que pensé que podría ser posible de alguna manera. Presumiblemente, podría ser pirateado a través de la reflexión (¿no es que todos usen ese método)?

No estoy necesariamente interesado en implementar un método dado, simplemente provocó mi curiosidad cuando se me ocurrió :-)

alastairs
fuente
¿Responde esto a tu pregunta? Enumeración "Herencia"
T.Todua

Respuestas:

103

La razón por la que no puede extender Enums es porque daría lugar a problemas de polimorfismo.

Digamos que tiene una enumeración MyEnum con valores A, B y C, y extiéndala con el valor D como MyExtEnum.

Supongamos que un método espera un valor myEnum en algún lugar, por ejemplo, como parámetro. Debería ser legal proporcionar un valor MyExtEnum, porque es un subtipo, pero ahora ¿qué va a hacer cuando resulte que el valor es D?

Para eliminar este problema, extender enumeraciones es ilegal

Rik
fuente
4
En realidad, la razón es simplemente que no tiene ningún sentido. El problema que mencionas, es decir, el código de cliente que recibe una constante que no espera, todavía existe con la implementación actual; de hecho, el compilador no te permite los switchvalores de enumeración sin proporcionar un defaultcaso o lanzar una excepción. E, incluso si lo hiciera, la enumeración en tiempo de ejecución podría provenir de una compilación separada
Raffaele
@Raffaele Acabo de probar esto, y al menos en Java 7, puede activar una enumeración sin un defaultcaso o lanzamiento.
Dathan
@Dathan, ¡definitivamente tienes razón! Tal como está escrito, eso es simplemente incorrecto, ¿tal vez debería eliminarlo? Estaba pensando en el caso en el que usa a switchpara proporcionar un valor requerido, como la inicial de un local, o toreturn
Raffaele
3
Esto no es personal, Rik. ¡Pero esta es prácticamente la peor razón! Polimorfismo y enumeración ...DayOfWeek a = (DayOfWeek) 1; DayOfWeek b = (DayOfWeek) 4711; Console.WriteLine(a + ", " + b);
Bitterblue
41

Cuando las enumeraciones integradas no son suficientes, puede hacerlo a la antigua y crear las suyas propias. Por ejemplo, si desea agregar una propiedad adicional, por ejemplo, un campo de descripción, puede hacerlo de la siguiente manera:

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

Luego puede tratarlo como una enumeración así:

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

El truco consiste en asegurarse de que el constructor sea privado (o esté protegido si desea heredar) y que sus instancias sean estáticas.

tsimon
fuente
No soy una persona de C #, pero ¿no quieres un poco de final (o sellado / const / lo que sea) allí?
Tom Hawtin - tackline
1
Yo no lo creo. O, al menos, no para la situación en la que querría heredar de esta clase para agregar nuevos valores de "enumeración". Prevención de la herencia final y sellada IIRC.
alastairs
4
@alastairs Creo que se refería a agregar finala la public static Action DoIt = new Action("Do it", "This does things");línea, en lugar de a la clase.
aplastar
1
Como en agregando readonly, ala:public static readonly Action DoIt = new Action("Do it", "This does things");
ErikE
40

Vas por el camino equivocado: una subclase de una enumeración tendría menos entradas.

En pseudocódigo, piense:

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

Cualquier método que pueda aceptar un Animal debería poder aceptar un Mamífero, pero no al revés. La subclasificación es para hacer algo más específico, no más general. Es por eso que "objeto" es la raíz de la jerarquía de clases. Del mismo modo, si las enumeraciones fueran heredables, entonces una raíz hipotética de la jerarquía de enumeraciones tendría todos los símbolos posibles.

Pero no, C # / Java no permite subenums, AFAICT, aunque a veces sería realmente útil. Probablemente sea porque eligieron implementar Enums como ints (como C) en lugar de símbolos internos (como Lisp). (Arriba, ¿qué representa (Animal) 1 y qué representa (Mammal) 1, y tienen el mismo valor?)

Sin embargo, podría escribir su propia clase enum (con un nombre diferente) que proporcione esto. Con los atributos de C #, incluso podría verse bastante bien.

Conocido
fuente
3
análisis interesante. ¡Nunca lo había pensado de esta manera!
Nerrve
Interesante, pero creo que está mal. Dar a entender que debería haber menos tipos en una subclase tiene sentido en términos de árboles de clasificación de animales, pero no en términos de código. Cuando sublcasas en código, nunca hay menos métodos. Nunca hay menos variables miembro. Su argumento no parece aplicarse al software como a las clasificaciones de animales.
Kieveli
4
@Kieveli, tu análisis es incorrecto. Agregar un miembro no tiene el mismo efecto en un objeto que agregar al conjunto de sus posibles valores. Esto no es algo que se haya inventado Ken; Existen reglas claras y precisas de la informática que explican por qué funciona de esta manera. Intente buscar covarianza y contravarianza (no solo la masturbación académica, le ayudará a comprender las reglas para cosas como firmas de funciones y contenedores).
jwg
@Kieveli Suponga que tiene una clase 'StreamWriter' (toma flujo - escribe flujo). Crearía 'TextWriter: StreamWriter' (toma flujo - escribe texto). Ahora crea 'HtmlWriter: TextWriter' (toma flujo - escribe bastante html). Ahora el HtmlWriter obviamente tiene más miembros, métodos, etcétera. Ahora intente tomar una transmisión de una tarjeta de sonido y escribirla en un archivo usando HtmlWriter :)
evictednoise
Este ejemplo es artificial y no prueba que nunca debería haber MÁS valores de enumeración en la clase extendida. enum VehicalParts {License, Speed, Capacity}; enumeración CarParts {SteeringWheel, Winshield}; + todo lo que Vehical tiene también
Bernoulli Lizard
12

Se supone que las enumeraciones representan la enumeración de todos los valores posibles, por lo que extender más bien va en contra de la idea.

Sin embargo, lo que puede hacer en Java (y presumiblemente C ++ 0x) es tener una interfaz en lugar de una clase de enumeración. Luego, coloque valores estándar en una enumeración que implemente la función. Obviamente, no puedes usar java.util.EnumSet y similares. Este es el enfoque adoptado en "más funciones NIO", que debería estar en 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
fuente
4

Puede usar la reflexión .NET para recuperar las etiquetas y valores de una enumeración existente en tiempo de ejecución ( Enum.GetNames()y Enum.GetValues()son los dos métodos específicos que usaría) y luego usar la inyección de código para crear uno nuevo con esos elementos más algunos nuevos. Esto parece algo análogo a "heredar de una enumeración existente".

McKenzieG1
fuente
2

Agregar enumeraciones es algo bastante común si regresa al código fuente y edita, cualquier otra forma (herencia o reflexión, si es posible) es probable que regrese y lo golpee cuando obtenga una actualización de la biblioteca y han introducido el mismo nombre de enumeración o el mismo valor de enumeración: he visto un montón de código de bajo nivel donde el número entero coincide con la codificación binaria, donde se encontrarían problemas

Idealmente, las enumeraciones de referencia de código deben escribirse solo como iguales (o conmutadores), y tratar de ser a prueba de futuro al no esperar que el conjunto de enumeraciones sea constante

Oskar
fuente
2

Si te refieres a se extiende en el sentido de la clase Base, entonces en Java ... no.

Pero puede extender un valor de enumeración para tener propiedades y métodos si eso es lo que quiere decir.

Por ejemplo, lo siguiente usa una enumeración de soporte:

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
fuente
Esto suena como un tipo de valor (estructura) en .NET. No sabía que pudieras hacer esto en Java.
alastairs
2

No vi a nadie más mencionar esto, pero el valor ordinal de una enumeración es importante. Por ejemplo, con grails cuando guarda una enumeración en la base de datos, usa el valor ordinal. Si de alguna manera pudiera extender una enumeración, ¿cuáles serían los valores ordinales de sus extensiones? Si lo extendiera en varios lugares, ¿cómo podría conservar algún tipo de orden en estos ordinales? El caos / inestabilidad en los valores ordinales sería algo malo, lo que probablemente sea otra razón por la que los diseñadores del lenguaje no han tocado esto.

Otra dificultad si fueras el diseñador del lenguaje, ¿cómo puedes preservar la funcionalidad del método values ​​() que se supone que devuelve todos los valores de enumeración? ¿En qué lo invocarías y cómo reuniría todos los valores?

Darrell Denlinger
fuente
0

Hmmm, hasta donde yo sé, esto no se puede hacer, las enumeraciones se escriben en tiempo de diseño y se utilizan para conveniencia del programador.

Estoy bastante seguro de que cuando se compile el código, los valores equivalentes serán sustituidos por los nombres en su enumeración, eliminando así el concepto de enumeración y (por lo tanto) la capacidad de extenderlo.

Chris Roberts
fuente
0

Me gustaría poder agregar valores a las enumeraciones de C # que son combinaciones de valores existentes. Por ejemplo (esto es lo que quiero hacer):

AnchorStyles se define como

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

y me gustaría agregar un AnchorStyles.BottomRight = Right + Bottom en lugar de decir

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

Solo puedo decir

my_ctrl.Anchor = AnchorStyles.BottomRight;

Esto no causa ninguno de los problemas que se han mencionado anteriormente, por lo que sería bueno si fuera posible.

Botella doble
fuente
0

Hace algún tiempo, incluso yo quería hacer algo como esto y descubrí que las extensiones de enumeración anularían muchos conceptos básicos ... (no solo polimorfismo)

Pero aún así, es posible que deba hacerlo si la enumeración se declara en una biblioteca externa y recuerde que debe tener una precaución especial al usar estas extensiones de enumeración ...

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án
fuente
0

En lo que respecta a Java, no está permitido porque agregar elementos a una enumeración crearía efectivamente una superclase en lugar de una subclase.

Considerar:

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

Un caso de uso general de polimorfismo sería

 Person person = Student.ROSS;   //not legal

lo cual es claramente incorrecto.

Aniket Thakur
fuente
0

Una solución temporal / local , cuando solo desea un uso muy 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   );

Vea todas las respuestas en: Enum "Herencia"

T.Todua
fuente