Estensione di un'enumerazione tramite ereditarietà


88

So che questo va piuttosto contro l'idea di enumerazioni, ma è possibile estendere le enumerazioni in C # / Java? Intendo "estendere" sia nel senso di aggiungere nuovi valori a un enum, ma anche nel senso OO di ereditare da un enum esistente.

Presumo che non sia possibile in Java, poiché li ha ricevuti solo di recente (Java 5?). C # sembra più indulgente con le persone che vogliono fare cose folli, quindi ho pensato che in qualche modo sarebbe stato possibile. Presumibilmente potrebbe essere violato tramite riflessione (non che tutti usereste effettivamente quel metodo)?

Non sono necessariamente interessato a implementare un determinato metodo, ha solo provocato la mia curiosità quando mi è venuto in mente :-)


Questo risponde alla tua domanda? Enum "Inheritance"
T.Todua

Risposte:


105

Il motivo per cui non puoi estendere Enums è perché porterebbe a problemi con il polimorfismo.

Supponiamo di avere un'enumerazione MyEnum con i valori A, B e C e di estenderla con il valore D come MyExtEnum.

Supponiamo che un metodo si aspetti un valore myEnum da qualche parte, ad esempio come parametro. Dovrebbe essere legale fornire un valore MyExtEnum, perché è un sottotipo, ma ora cosa farai quando scoprirai che il valore è D?

Per eliminare questo problema, l'estensione delle enumerazioni è illegale


4
In realtà, il motivo è semplicemente che non ha alcun senso. Il problema che hai menzionato, cioè il codice client che riceve una costante che non si aspetta, esiste ancora con l'implementazione corrente - infatti il ​​compilatore non ti lascia switchsui valori enum senza fornire un defaultcaso o lanciare un'eccezione. E, anche se così fosse, l'enumerazione in fase di esecuzione potrebbe provenire da una compilation separata
Raffaele

@Raffaele ho appena provato questo, e almeno in Java 7, puoi attivare un enum senza defaultcase o lancio.
Dathan

@Dathan hai decisamente ragione! Come è scritto, è semplicemente sbagliato: forse dovrei rimuoverlo? Stavo pensando al caso quando usi a switchper fornire un valore richiesto, come l'iniziale di un locale, oppure areturn
Raffaele

3
Non è personale, Rik. Ma questo è praticamente il peggior motivo in assoluto! Polimorfismo ed enumerazione ...DayOfWeek a = (DayOfWeek) 1; DayOfWeek b = (DayOfWeek) 4711; Console.WriteLine(a + ", " + b);
Bitterblue

41

Stai andando nella direzione sbagliata: una sottoclasse di un'enumerazione avrebbe meno voci.

In pseudocodice, pensa:

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

Qualsiasi metodo che possa accettare un animale dovrebbe essere in grado di accettare un mammifero, ma non il contrario. La sottoclasse serve per creare qualcosa di più specifico, non più generale. Ecco perché "oggetto" è la radice della gerarchia di classi. Allo stesso modo, se le enumerazioni fossero ereditabili, un'ipotetica radice della gerarchia degli enum avrebbe tutti i simboli possibili.

Ma no, C # / Java non consente enumerazioni secondarie, AFAICT, anche se a volte sarebbe davvero utile. Probabilmente è perché hanno scelto di implementare Enums come int (come C) invece di simboli interni (come Lisp). (Sopra, cosa rappresenta (Animale) 1 e cosa rappresenta (Mammifero) 1, e hanno lo stesso valore?)

Tuttavia, potresti scrivere la tua classe simile a enum (con un nome diverso) che lo fornisca. Con gli attributi C # potrebbe anche sembrare carino.


3
analisi interessante. mai pensato in questo modo!
Nerrve

Interessante, ma penso che sia sbagliato. Insinuare che dovrebbero esserci meno tipi in una sottoclasse ha senso in termini di alberi di classificazione degli animali, ma non in termini di codice. Quando si sottoclasse nel codice, non ci sono mai meno metodi. Non ci sono mai meno variabili membro. Il tuo argomento non sembra applicarsi al software come accade alle classificazioni degli animali.
Kieveli

4
@Kieveli, la tua analisi è sbagliata. L'aggiunta di un membro non ha lo stesso effetto su un oggetto dell'aggiunta all'insieme dei suoi possibili valori. Questo non è qualcosa che Ken ha inventato; ci sono regole chiare e precise dell'informatica che spiegano perché funziona in questo modo. Prova a cercare covarianza e controvarianza (non solo masturbazione accademica, ti aiuterà a capire le regole per cose come firme di funzioni e contenitori).
jwg

@Kieveli Supponi di avere una classe "StreamWriter" (accetta stream - scrive stream). Dovresti creare "TextWriter: StreamWriter" (prende il flusso - scrive il testo). Ora crei "HtmlWriter: TextWriter" (prende il flusso - scrive abbastanza html). Ora HtmlWriter ha ovviamente più membri, metodi e così via. Ora prova a prendere un flusso da una scheda audio e a scriverlo su file usando HtmlWriter :)
evictednoise

Questo esempio è artificioso e non prova che non dovrebbero mai esserci più valori enum nella classe estesa. enum VehicalParts {License, Speed, Capacity}; enum CarParts {SteeringWheel, Winshield}; + tutto quello che ha anche Vehical
Bernoulli Lizard

41

Quando le enumerazioni incorporate non sono sufficienti, puoi farlo alla vecchia maniera e crearne di tue. Ad esempio, se desideri aggiungere una proprietà aggiuntiva, ad esempio un campo descrizione, puoi farlo come segue:

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

Puoi quindi trattarlo come un enum in questo modo:

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

Il trucco è assicurarsi che il costruttore sia privato (o protetto se si desidera ereditare) e che le istanze siano statiche.


Non sono una persona C #, ma non vuoi un po 'di finale (o sealed / const / qualunque) lì dentro?
Tom Hawtin - tackline

1
Non credo proprio. O, almeno, non per la situazione in cui vorresti ereditare da questa classe per aggiungere nuovi valori "enum". Finale e sigillato impediscono l'eredità IIRC.
alastairs

4
@alastairs Penso che intendesse aggiungere finalalla public static Action DoIt = new Action("Do it", "This does things");linea, piuttosto che alla classe.
schiacciare il

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

12

Si suppone che le enumerazioni rappresentino l'enumerazione di tutti i valori possibili, quindi l'estensione va contro l'idea.

Tuttavia, ciò che puoi fare in Java (e presumibilmente C ++ 0x) è avere un'interfaccia invece di una classe enum. Quindi inserisci i valori standard in un'enumerazione che implementa la funzionalità. Ovviamente non puoi usare java.util.EnumSet e simili. Questo è l'approccio adottato in "più funzionalità NIO", che dovrebbe essere in JDK7.

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


public enum WTFResults implements Result {
    FILE_NOT_FOUND
}

4

Puoi usare .NET reflection per recuperare le etichette ei valori da un'enumerazione esistente in fase di esecuzione ( Enum.GetNames()e Enum.GetValues()sono i due metodi specifici che useresti) e quindi usare l'inserimento di codice per crearne uno nuovo con quegli elementi più alcuni nuovi. Questo sembra in qualche modo analogo a "ereditare da un enum esistente".


2

L'aggiunta di enumerazioni è una cosa abbastanza comune da fare se torni al codice sorgente e modifichi, qualsiasi altro modo (ereditarietà o riflessione, se possibile) è probabile che torni e ti colpisca quando ottieni un aggiornamento della libreria e hanno introdotto lo stesso nome enum o lo stesso valore enum - Ho visto un sacco di codice di basso livello in cui il numero intero corrisponde alla codifica binaria, dove potresti incorrere in problemi

Idealmente il codice che fa riferimento alle enumerazioni dovrebbe essere scritto solo come uguale (o opzioni) e cercare di essere a prova di futuro non aspettandosi che il set enum sia const


2

Se intendi extends nel senso della classe Base, allora in Java ... no.

Ma puoi estendere un valore enum per avere proprietà e metodi se questo è ciò che intendi.

Ad esempio, quanto segue utilizza un'enumerazione di parentesi:

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

Sembra un tipo di valore (struct) in .NET. Non sapevo che potessi farlo in Java.
alastairs

2

Non ho visto nessun altro menzionarlo, ma il valore ordinale di un enum è importante. Ad esempio, con grails quando si salva un'enumerazione nel database, viene utilizzato il valore ordinale. Se potessi in qualche modo estendere un'enumerazione, quali sarebbero i valori ordinali delle tue estensioni? Se lo estendessi in più punti, come potresti mantenere un qualche tipo di ordine per questi ordinali? Il caos / instabilità nei valori ordinali sarebbe una cosa negativa che probabilmente è un'altra ragione per cui i progettisti del linguaggio non hanno toccato questo aspetto.

Un'altra difficoltà se tu fossi il progettista del linguaggio, come puoi preservare la funzionalità del metodo values ​​() che dovrebbe restituire tutti i valori enum. Su cosa lo invoceresti e come raccoglieresti tutti i valori?




0

Hmmm - per quanto ne so, questo non può essere fatto - le enumerazioni vengono scritte in fase di progettazione e vengono utilizzate per comodità del programmatore.

Sono abbastanza sicuro che quando il codice viene compilato, i valori equivalenti verranno sostituiti ai nomi nella tua enumerazione, rimuovendo così il concetto di enumerazione e (quindi) la possibilità di estenderlo.


0

Mi piacerebbe essere in grado di aggiungere valori alle enumerazioni C # che sono combinazioni di valori esistenti. Ad esempio (questo è quello che voglio fare):

AnchorStyles è definito come

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

e vorrei aggiungere un AnchorStyles.BottomRight = Right + Bottom così invece di dire

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

Posso solo dire

my_ctrl.Anchor = AnchorStyles.BottomRight;

Ciò non causa nessuno dei problemi sopra menzionati, quindi sarebbe bello se fosse possibile.


0

Qualche tempo fa anche io volevo fare qualcosa del genere e ho scoperto che le estensioni enum avrebbero annullato molti concetti di base ... (non solo polimorfismo)

Ma potresti comunque doverlo fare se l'enum è dichiarato nella libreria esterna e ricorda che dovresti fare una particolare attenzione quando usi queste estensioni 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

Per quanto riguarda java non è consentito perché l'aggiunta di elementi a un enum creerebbe effettivamente una super classe piuttosto che una sottoclasse.

Ritenere:

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

Un caso d'uso generale del polimorfismo sarebbe

 Person person = Student.ROSS;   //not legal

il che è chiaramente sbagliato.


0

Una soluzione temporanea / locale , quando vuoi solo un utilizzo molto locale / una tantum:

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

Vedi tutte le risposte su: Enum "Inheritance"

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.