Rozszerzanie wyliczenia przez dziedziczenie

85

Wiem, że jest to raczej sprzeczne z ideą wyliczeń, ale czy można rozszerzyć wyliczenia w C # / Javie? Mam na myśli „rozszerzanie” zarówno w sensie dodawania nowych wartości do wyliczenia, ale także w sensie OO dziedziczenia z istniejącego wyliczenia.

Zakładam, że nie jest to możliwe w Javie, ponieważ dostał je dopiero niedawno (Java 5?). C # wydaje się jednak bardziej wyrozumiały dla ludzi, którzy chcą robić szalone rzeczy, więc pomyślałem, że może to być w jakiś sposób możliwe. Przypuszczalnie można to zhakować przez refleksję (nie żeby wszyscy faktycznie używali tej metody)?

Niekoniecznie jestem zainteresowany wdrożeniem jakiejś metody, po prostu wzbudziło moją ciekawość, gdy przyszło mi do głowy :-)

alastairs
źródło
Czy to odpowiada na twoje pytanie? Enum „Inheritance”
T.Todua

Odpowiedzi:

103

Powodem, dla którego nie możesz rozszerzyć wyliczeń, jest to, że prowadziłoby to do problemów z polimorfizmem.

Powiedzmy, że masz wyliczenie MyEnum z wartościami A, B i C i rozszerz je o wartość D jako MyExtEnum.

Załóżmy, że metoda oczekuje gdzieś wartości myEnum, na przykład jako parametru. Podanie wartości MyExtEnum powinno być legalne, ponieważ jest to podtyp, ale co teraz zrobisz, gdy okaże się, że wartość to D?

Aby wyeliminować ten problem, rozszerzanie wyliczeń jest nielegalne

Rik
źródło
4
Właściwie to po prostu nie ma sensu. Problem, o którym wspomniałeś, tj. Kod klienta otrzymujący stałą, której się nie spodziewał, nadal istnieje w bieżącej implementacji - w rzeczywistości kompilator nie pozwala switchna wartości wyliczenia bez podania defaultwielkości liter lub zgłoszenia wyjątku. A nawet gdyby tak było, wyliczenie w czasie wykonywania mogłoby pochodzić z oddzielnej kompilacji
Raffaele
@Raffaele Właśnie to wypróbowałem i przynajmniej w Javie 7 możesz włączyć wyliczenie bez defaultwielkości liter i rzucania.
Datan
@Dathan, zdecydowanie masz rację! Jak napisano, to po prostu źle - może powinienem to usunąć? Myślałem o przypadku, gdy używasz a, switchaby podać wymaganą wartość, taką jak inicjał lokalnego, lub doreturn
Raffaele
3
To nie jest osobiste, Rik. Ale to najgorszy powód w historii! Polimorfizm i wyliczenie ...DayOfWeek a = (DayOfWeek) 1; DayOfWeek b = (DayOfWeek) 4711; Console.WriteLine(a + ", " + b);
Bitterblue
41

Gdy wbudowane wyliczenia nie wystarczą, możesz zrobić to w staromodny sposób i stworzyć własne. Na przykład, jeśli chcesz dodać dodatkową właściwość, na przykład pole opisu, możesz to zrobić w następujący sposób:

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

Następnie możesz traktować to jak wyliczenie w następujący sposób:

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

Sztuczka polega na upewnieniu się, że konstruktor jest prywatny (lub chroniony, jeśli chcesz dziedziczyć), a instancje są statyczne.

tsimon
źródło
Nie jestem osobą C #, ale czy nie chcesz tam trochę ostatecznego (lub zapieczętowanego / const / cokolwiek)?
Tom Hawtin - tackline
1
Nie wierzę w to. A przynajmniej nie w sytuacji, w której chciałbyś dziedziczyć z tej klasy, aby dodać nowe wartości „wyliczenia”. Ostateczne i zapieczętowane zapobiegają dziedziczeniu IIRC.
alastairs
4
@alastairs Myślę, że miał na myśli dodanie finaldo public static Action DoIt = new Action("Do it", "This does things");wiersza, a nie klasy.
zmiażdżyć
1
Jak dodając readonly, ala:public static readonly Action DoIt = new Action("Do it", "This does things");
ErikE
40

Idziesz w złą stronę: podklasa wyliczenia miałaby mniej wpisów.

W pseudokodzie pomyśl:

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

Każda metoda, która może zaakceptować zwierzę, powinna być w stanie zaakceptować ssaka, ale nie na odwrót. Podklasy służą do tworzenia czegoś bardziej szczegółowego, a nie bardziej ogólnego. Dlatego „obiekt” jest korzeniem hierarchii klas. Podobnie, gdyby wyliczenia były dziedziczone, wówczas hipotetyczny pierwiastek hierarchii wyliczeń miałby każdy możliwy symbol.

Ale nie, C # / Java nie zezwalają na wyliczenia podrzędne, AFAICT, chociaż czasami byłoby to naprawdę przydatne. Prawdopodobnie dlatego, że zdecydowali się zaimplementować wyliczenia jako wartości typu int (jak C) zamiast symboli internowanych (jak Lisp). (Powyżej, co reprezentuje (Animal) 1 i co reprezentuje (Mammal) 1 i czy mają tę samą wartość?)

Możesz jednak napisać własną klasę typu wyliczenia (pod inną nazwą), która to zapewniła. Z atrybutami C # może to nawet wyglądać ładnie.

Rozpoznać
źródło
3
ciekawa analiza. nigdy nie myślałem o tym w ten sposób!
Nerrve
Ciekawe, ale myślę, że to źle. Sugerowanie, że w podklasie powinno być mniej typów, ma sens z punktu widzenia drzew klasyfikacji zwierząt, ale nie w kategoriach kodu. Kiedy podklasa w kodzie jest podrzędna, nigdy nie ma mniej metod. Nigdy nie ma mniej zmiennych składowych. Twój argument nie wydaje się odnosić do oprogramowania, tak jak do klasyfikacji zwierząt.
Kieveli
4
@Kieveli, twoja analiza jest błędna. Dodanie członka nie ma takiego samego wpływu na obiekt, jak dodanie do zestawu jego możliwych wartości. To nie jest coś, co wymyślił Ken; istnieją jasne i precyzyjne zasady informatyki, które wyjaśniają, dlaczego tak działa. Spróbuj wyszukać kowariancję i kontrawariancję (nie tylko masturbację akademicką, ale pomoże ci zrozumieć zasady dotyczące rzeczy takich jak sygnatury funkcji i pojemniki).
jwg
@Kieveli Załóżmy, że masz klasę „StreamWriter” (pobiera strumień - zapisuje strumień). Utworzyłbyś „TextWriter: StreamWriter” (pobiera strumień - zapisuje tekst). Teraz tworzysz „HtmlWriter: TextWriter” (pobiera strumień - pisze ładny html). Teraz HtmlWriter ma oczywiście więcej członków, metod i tak dalej. Teraz spróbuj pobrać strumień z karty dźwiękowej i zapisać go do pliku za pomocą HtmlWriter :)
evictednoise
Ten przykład jest sztuczny i nie dowodzi, że w klasie rozszerzonej nigdy nie powinno być WIĘCEJ wartości wyliczeniowych. enum VehicalParts {licencja, prędkość, pojemność}; enum CarParts {SteeringWheel, Winshield}; + wszystko, co ma też Vehical
Bernoulli Lizard
12

Wyliczenia mają reprezentować wyliczenie wszystkich możliwych wartości, więc rozszerzanie raczej jest sprzeczne z ideą.

Jednak to, co możesz zrobić w Javie (i prawdopodobnie w C ++ 0x), to mieć interfejs zamiast klasy wyliczeniowej. Następnie umieść standardowe wartości w wyliczeniu, które implementuje tę funkcję. Oczywiście nie możesz używać java.util.EnumSet i tym podobnych. Jest to podejście przyjęte w „większej liczbie funkcji NIO”, które powinny znaleźć się w 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 - haczyk
źródło
4

Możesz użyć odbicia .NET, aby pobrać etykiety i wartości z istniejącego wyliczenia w czasie wykonywania ( Enum.GetNames()i Enum.GetValues()są to dwie konkretne metody, których użyjesz), a następnie użyj wstrzyknięcia kodu, aby utworzyć nowy z tymi elementami oraz kilkoma nowymi. Wydaje się to nieco analogiczne do „dziedziczenia z istniejącego wyliczenia”.

McKenzieG1
źródło
2

Dodawanie wyliczeń jest dość powszechną czynnością, jeśli wrócisz do kodu źródłowego i edytujesz, każdy inny sposób (dziedziczenie lub odbicie, jeśli jest to możliwe) prawdopodobnie powróci i uderzy, gdy otrzymasz aktualizację biblioteki i wprowadzili tę samą nazwę wyliczenia lub tę samą wartość wyliczenia - widziałem wiele kodów niskiego poziomu, w których liczba całkowita jest zgodna z kodowaniem binarnym, co powoduje problemy

Idealnie byłoby, gdyby wyliczenia odwołujące się do kodu były zapisywane tylko jako równe (lub przełączniki) i staraj się być przyszłościowe, nie oczekując, że zestaw wyliczenia będzie stały

Oskar
źródło
2

Jeśli masz na myśli rozszerzenia w sensie klasy bazowej, to w Javie ... nie.

Ale możesz rozszerzyć wartość wyliczenia, aby mieć właściwości i metody, jeśli to masz na myśli.

Na przykład w poniższym przykładzie użyto wyliczenia w nawiasie:

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
źródło
To brzmi jak typ wartości (struktura) w .NET. Nie wiedziałem, że możesz to zrobić w Javie.
alastairs
2

Nie widziałem, żeby ktokolwiek o tym wspominał, ale wartość porządkowa wyliczenia jest ważna. Na przykład w przypadku Grails, gdy zapisujesz wyliczenie w bazie danych, używa wartości porządkowej. Gdybyś mógł w jakiś sposób rozszerzyć wyliczenie, jakie byłyby wartości porządkowe twoich rozszerzeń? Gdybyś rozszerzył go w wielu miejscach, jak mógłbyś zachować jakiś porządek w tych liczbach porządkowych? Chaos / niestabilność wartości porządkowych byłaby złą rzeczą, co jest prawdopodobnie kolejnym powodem, dla którego projektanci języka tego nie dotykali.

Inna trudność, gdybyś był projektantem języka, jak zachować funkcjonalność metody values ​​(), która ma zwrócić wszystkie wartości wyliczenia. Do czego byś się przywołał i jak zebrałby wszystkie wartości?

Darrell Denlinger
źródło
0

Nie możesz dziedziczyć z / rozszerzać wyliczenia, możesz użyć atrybutów do zadeklarowania opisu . Jeśli szukasz wartości całkowitej, to jest to wbudowane.

bdukes
źródło
0

Hmmm - z tego co wiem, nie da się tego zrobić - wyliczenia są pisane na etapie projektowania i są używane jako ułatwienie dla programisty.

Jestem prawie pewien, że podczas kompilacji kodu równoważne wartości zostaną zastąpione nazwami w wyliczeniu, usuwając w ten sposób pojęcie wyliczenia i (tym samym) możliwość jego rozszerzenia.

Chris Roberts
źródło
0

Chciałbym móc dodawać wartości do wyliczeń C #, które są kombinacjami istniejących wartości. Na przykład (to jest to, co chcę zrobić):

AnchorStyles jest zdefiniowany jako

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

i chciałbym dodać AnchorStyles.BottomRight = Right + Bottom, więc zamiast mówić

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

Mogę tylko powiedzieć

my_ctrl.Anchor = AnchorStyles.BottomRight;

Nie powoduje to żadnego z wyżej wymienionych problemów, więc byłoby miło, gdyby było to możliwe.

Magnum
źródło
0

Jakiś czas temu nawet ja chciałem zrobić coś takiego i odkryłem, że rozszerzenia wyliczenia rozwarłyby wiele podstawowych pojęć ... (nie tylko polimorfizm)

Ale nadal możesz potrzebować zrobić, jeśli wyliczenie jest zadeklarowane w zewnętrznej bibliotece i pamiętaj, że powinieneś zachować szczególną ostrożność podczas korzystania z tego rozszerzenia wyliczenia ...

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;
   }
}
Sułtan
źródło
0

Jeśli chodzi o java, jest to niedozwolone, ponieważ dodanie elementów do wyliczenia spowodowałoby efektywne utworzenie superklasy, a nie podklasy.

Rozważać:

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

Ogólny przypadek użycia polimorfizmu byłby

 Person person = Student.ROSS;   //not legal

co jest ewidentnie błędne.

Aniket Thakur
źródło
0

Tymczasowe / lokalne obejście , gdy chcesz tylko bardzo lokalne / jednorazowe użycie:

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

Zobacz wszystkie odpowiedzi na: Enum „Dziedziczenie”

T.Todua
źródło