C # против Java Enum (для новичков в C #)

Некоторое время я программировал на Java и только что меня бросили в проект, полностью написанный на C #. Я пытаюсь разобраться в C # и заметил, что перечисления используются в нескольких местах в моем новом проекте, но на первый взгляд перечисления C # кажутся более упрощенными, чем реализация Java 1.5+. Может ли кто-нибудь перечислить различия между перечислениями C # и Java и как их преодолеть? (Я не хочу начинать войну языкового пламени, я просто хочу знать, как делать некоторые вещи на C #, которые я делал на Java). Например, может ли кто-нибудь опубликовать аналог на C # известного примера перечисления Sun Planet?

public enum Planet {
  MERCURY (3.303e+23, 2.4397e6),
  VENUS   (4.869e+24, 6.0518e6),
  EARTH   (5.976e+24, 6.37814e6),
  MARS    (6.421e+23, 3.3972e6),
  JUPITER (1.9e+27,   7.1492e7),
  SATURN  (5.688e+26, 6.0268e7),
  URANUS  (8.686e+25, 2.5559e7),
  NEPTUNE (1.024e+26, 2.4746e7),
  PLUTO   (1.27e+22,  1.137e6);

  private final double mass;   // in kilograms
  private final double radius; // in meters
  Planet(double mass, double radius) {
      this.mass = mass;
      this.radius = radius;
  }
  public double mass()   { return mass; }
  public double radius() { return radius; }

  // universal gravitational constant  (m3 kg-1 s-2)
  public static final double G = 6.67300E-11;

  public double surfaceGravity() {
      return G * mass / (radius * radius);
  }
  public double surfaceWeight(double otherMass) {
      return otherMass * surfaceGravity();
  }
}

// Example usage (slight modification of Sun's example):
public static void main(String[] args) {
    Planet pEarth = Planet.EARTH;
    double earthRadius = pEarth.radius(); // Just threw it in to show usage

    // Argument passed in is earth Weight.  Calculate weight on each planet:
    double earthWeight = Double.parseDouble(args[0]);
    double mass = earthWeight/pEarth.surfaceGravity();
    for (Planet p : Planet.values())
       System.out.printf("Your weight on %s is %f%n",
                         p, p.surfaceWeight(mass));
}

// Example output:
$ java Planet 175
Your weight on MERCURY is 66.107583
Your weight on VENUS is 158.374842
[etc ...]

person Ogre Psalm33    schedule 22.01.2009    source источник
comment
@ycomp Я не могу поверить в это. Он исходит от Sun (теперь Oracle): docs.oracle.com/javase /tutorial/java/javaOO/enum.html   -  person Ogre Psalm33    schedule 22.09.2015


Ответы (12)


Перечисления в CLR - это просто именованные константы. Базовый тип должен быть целым. В Java перечисление больше похоже на именованный экземпляр типа. Этот тип может быть довольно сложным и, как показывает ваш пример, содержать несколько полей разных типов.

Чтобы перенести пример на C #, я бы просто изменил перечисление на неизменяемый класс и предоставил статические экземпляры этого класса только для чтения:

using System;
using System.Collections.Generic;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Planet planetEarth = Planet.MERCURY;

            double earthRadius = pEarth.Radius; // Just threw it in to show usage
            double earthWeight = double.Parse("123");
            double earthMass   = earthWeight / pEarth.SurfaceGravity();

            foreach (Planet p in Planet.Values)
                Console.WriteLine($"Your weight on {p} is {p.SurfaceWeight(mass)}");

            Console.ReadKey();
        }
    }

    public class Planet
    {
        public static readonly Planet MERCURY = new Planet("Mercury", 3.303e+23, 2.4397e6);
        public static readonly Planet VENUS   = new Planet("Venus", 4.869e+24, 6.0518e6);
        public static readonly Planet EARTH   = new Planet("Earth", 5.976e+24, 6.37814e6);
        public static readonly Planet MARS    = new Planet("Mars", 6.421e+23, 3.3972e6);
        public static readonly Planet JUPITER = new Planet("Jupiter", 1.9e+27, 7.1492e7);
        public static readonly Planet SATURN  = new Planet("Saturn", 5.688e+26, 6.0268e7);
        public static readonly Planet URANUS  = new Planet("Uranus", 8.686e+25, 2.5559e7);
        public static readonly Planet NEPTUNE = new Planet("Neptune", 1.024e+26, 2.4746e7);
        public static readonly Planet PLUTO   = new Planet("Pluto", 1.27e+22, 1.137e6);

        public static IEnumerable<Planet> Values
        {
            get
            {
                yield return MERCURY;
                yield return VENUS;
                yield return EARTH;
                yield return MARS;
                yield return JUPITER;
                yield return SATURN;
                yield return URANUS;
                yield return NEPTUNE;
                yield return PLUTO;
            }
        }

        public string Name   { get; private set; }
        public double Mass   { get; private set; }
        public double Radius { get; private set; }

        Planet(string name, double mass, double radius) => 
            (Name, Mass, Radius) = (name, mass, radius);

        // Wniversal gravitational constant  (m3 kg-1 s-2)
        public const double G = 6.67300E-11;
        public double SurfaceGravity()            => G * mass / (radius * radius);
        public double SurfaceWeight(double other) => other * SurfaceGravity();
        public override string ToString()         => name;
    }
}
person Kent Boogaart    schedule 22.01.2009
comment
Это означает, что вы не можете использовать цикл for-each в перечислениях. - person Richard Walton; 22.01.2009
comment
Это своего рода типобезопасное перечисление, которое мы, бедняки, вынужденные использовать Java 1.4 и ниже, должны реализовать ... Перечисления Java 5, пожалуй, лучшая особенность Java 5+, тем более, что их можно использовать в операторах switch. - person MetroidFan2002; 22.01.2009
comment
Хорошо, из того, что я получил до сих пор, этот мне нравится больше всего. Это похоже на то, о чем я думал: если вам нужны методы перечисления в C #, либо измените их на константы (как вы), либо вставьте перечисление в служебный класс (что может быть труднее сделать с примером Planet). - person Ogre Psalm33; 22.01.2009
comment
Один очень незначительный момент заключается в том, что Microsoft предлагает использовать имена перечислений во множественном числе afaik. - person Chris S; 22.01.2009
comment
@Chris: во множественном числе должны быть только перечисления флагов. То есть перечисления, члены которых объединяются с помощью | оператор. - person Kent Boogaart; 05.04.2009
comment
Я все равно считаю этот пример глупым. Что, если вы захотите добавить новую планету? Вам придется отредактировать код. Вот почему вы должны использовать обычные классы для планет, а не перечисления в Java или C #. - person Mladen Mihajlovic; 07.04.2009
comment
@Mladen: Это полностью зависит от контекста. Перечисление планет может идеально подходить для игры, которая предоставляет доступ к ограниченному количеству планет. Изменения в коде могут быть именно тем, что вам нужно, если в игру добавляется новая планета. - person Kent Boogaart; 07.04.2009
comment
@Kent Верно, но мне все равно это не кажется хорошим дизайном. Если бы я хотел создать расширение для игры (например), я бы хотел, чтобы вся эта информация была отделена от исходного кода. Тем не менее, я не говорю, что для перечислений нет места, просто пример не подходит им в моих глазах. - person Mladen Mihajlovic; 08.04.2009
comment
@Richie_W вы можете перебирать перечисления, используя свойство Values. - person Jonathan; 06.12.2010
comment
@Richie_W у него есть чувство юмора. Чужой подход, понятно? - person Erix; 09.12.2011
comment
Ничего себе ... Практически невероятно увидеть что-то более подробное на C #, чем на Java. - person Sune Rasmussen; 25.09.2012
comment
@KentBoogaart Разве это не идеально для структуры вместо класса? - person nawfal; 08.05.2013
comment
Я думаю, что лучше использовать массив для значений вместо public static IEnumerable<Planet> Values, что-то вроде private static Plan[] values = {VIP, CHEAP, MEDIUM, D_PLAN};. Тогда он будет ближе к перечислению Java (вы можете иметь длину и получать доступ к каждому перечислению с помощью индекса) - person Fr4nz; 10.07.2013
comment
Используя этот пример, если бы вы хотели преобразовать имя планеты в виде строки в объект Planet, как бы вы это сделали? Я бы хотел сделать что-нибудь вроде var p = "Earth"; var enum = (Planet)p;. - person user1636130; 30.05.2016
comment
@nawfal На первый взгляд мне так показалось, но, в отличие от C #, java enum может иметь значение null. Я думаю, что class более эквивалентен с этой точки зрения. - person Attacktive; 26.07.2016
comment
@Suzi, Кроме того, в данном типе требуется double и double и ссылка, которая должна составлять до 20 байт. Не подходит для типа структуры, если он больше 16 байт, как правило, копирование и все такое. Мой первоначальный комментарий был довольно глупым :) - person nawfal; 26.07.2016
comment
Однако нельзя использовать в коммутаторе :( - person Sinaesthetic; 23.08.2016
comment
Это недоступно во время компиляции. Я не нашел способа использовать одну из планет в качестве значения по умолчанию для параметра функции. :( - person Zorgarath; 19.05.2017

В C # вы можете определять методы расширения для перечислений, и это компенсирует некоторые недостающие функции.

Вы можете определить Planet как перечисление, а также иметь методы расширения, эквивалентные surfaceGravity() и surfaceWeight().

Я использовал настраиваемые атрибуты, как было предложено Михаилом, но того же можно добиться с помощью словаря.

using System;
using System.Reflection;

class PlanetAttr: Attribute
{
    internal PlanetAttr(double mass, double radius)
    {
        this.Mass = mass;
        this.Radius = radius;
    }
    public double Mass { get; private set; }
    public double Radius { get; private set; }
}

public static class Planets
{
    public static double GetSurfaceGravity(this Planet p)
    {
        PlanetAttr attr = GetAttr(p);
        return G * attr.Mass / (attr.Radius * attr.Radius);
    }

    public static double GetSurfaceWeight(this Planet p, double otherMass)
    {
        return otherMass * p.GetSurfaceGravity();
    }

    public const double G = 6.67300E-11;

    private static PlanetAttr GetAttr(Planet p)
    {
        return (PlanetAttr)Attribute.GetCustomAttribute(ForValue(p), typeof(PlanetAttr));
    }

    private static MemberInfo ForValue(Planet p)
    {
        return typeof(Planet).GetField(Enum.GetName(typeof(Planet), p));
    }

}

public enum Planet
{
    [PlanetAttr(3.303e+23, 2.4397e6)]  MERCURY,
    [PlanetAttr(4.869e+24, 6.0518e6)]  VENUS,
    [PlanetAttr(5.976e+24, 6.37814e6)] EARTH,
    [PlanetAttr(6.421e+23, 3.3972e6)]  MARS,
    [PlanetAttr(1.9e+27,   7.1492e7)]  JUPITER,
    [PlanetAttr(5.688e+26, 6.0268e7)]  SATURN,
    [PlanetAttr(8.686e+25, 2.5559e7)]  URANUS,
    [PlanetAttr(1.024e+26, 2.4746e7)]  NEPTUNE,
    [PlanetAttr(1.27e+22,  1.137e6)]   PLUTO
}
person finnw    schedule 24.01.2011
comment
Думаю, за это нужно проголосовать больше. Это ближе к тому, как работают перечисления в Java. Я могу сделать что-то вроде Planet.MERCURY.GetSurfaceGravity () ‹- Обратите внимание на метод расширения в Enum! - person thenonhacker; 25.05.2011
comment
Определенно да. Методы расширения в Enums (черт возьми, методы расширения в целом) - отличное дополнение к C #. - person KeithS; 18.10.2011
comment
@AllonGuralnek Спасибо. Однако не все согласятся с метаданными. См. Комментарий MattDavey к соответствующему вопросу codereview.stackexchange. - person finnw; 21.10.2011
comment
@finnw: Я никогда не слышал о сайте Code Review SE - спасибо за это! Я продолжаю это обсуждение там (хотя, возможно, он заслуживает отдельного вопроса здесь, о Программистах). - person Allon Guralnek; 21.10.2011
comment
это отличное решение - кто-нибудь тестировал производительность этого? например сколько времени требуется для доступа к атрибуту по сравнению с доступом к свойству класса. - person Simon Meyer; 29.02.2016
comment
@SimonMeyer Конечно, вызывать Reflection непрактично - даже если оно кэшировано во время выполнения, с точки зрения производительности. Ответ высок, потому что он правильно ссылается на методы расширения ENUM как на ближайший аналог JAVA в C #. - person Lorenz Lo Sauer; 24.08.2016

В C # атрибуты можно использовать с перечислениями. Хороший пример этого шаблона программирования с подробным описанием можно найти здесь (Codeproject)

public enum Planet
{
   [PlanetAttr(3.303e+23, 2.4397e6)]
   Mercury,
   [PlanetAttr(4.869e+24, 6.0518e6)]
   Venus
} 

Изменить: этот вопрос недавно был снова задан, и на него ответил Джон Скит: Что эквивалентно перечислению Java в C #? Частные внутренние классы в C # - почему они не используются чаще?

Изменить 2: см. принятый ответ, который блестяще расширяет этот подход!

person Mikhail    schedule 12.09.2009
comment
Отлично! Это кажется немного неуклюжим, но в остальном это очень приемлемый метод добавления дополнительных данных в перечисление. Я искренне удивлен, что кому-то понадобилось столько времени, чтобы упомянуть об этом замечательном решении! - person Ogre Psalm33; 14.09.2009

Перечисления Java на самом деле являются полными классами, которые могут иметь частный конструктор, методы и т. Д., Тогда как перечисления C # - это просто целые числа. Реализация IMO Java намного превосходит.

Эта страница должна очень помочь вам при изучении C # из лагеря Java. (ссылка указывает на различия в перечислениях (прокрутите вверх / вниз для других вещей)

person Richard Walton    schedule 22.01.2009
comment
Хотя ваша ссылка дает интересный, обширный обзор сходств и различий между C # и Java, в тексте много ошибок (например, неверно указано, что Java protected соответствует внутреннему C #, тогда как он должен быть внутренним защищенным). Так что не принимайте все как должное :) - person mafu; 23.01.2009
comment
Я бы не сказал, что перечисления Java лучше, даже если я фанат java. C # поддерживает простое целочисленное объявление, такое как FOO = 0, которое проще использовать в инструментах ORM (ordinal() использование ошибок не требуется). Кроме того, C # поддерживает побитовые перечисления, которые часто очень полезны, особенно в сочетании с EntityFramework. Java должна расширить свои перечисления, чтобы также можно было связывать их с целыми числами. Тогда они были бы лучше :) - person djmj; 29.08.2015

Примерно так я думаю:

public class Planets 
{
    public static readonly Planet MERCURY = new Planet(3.303e+23, 2.4397e6);
    public static readonly Planet VENUS = new Planet(4.869e+24, 6.0518e6);
    public static readonly Planet EARTH = new Planet(5.976e+24, 6.37814e6);
    public static readonly Planet MARS = new Planet(6.421e+23, 3.3972e6);
    public static readonly Planet JUPITER = new Planet(1.9e+27,   7.1492e7);
    public static readonly Planet SATURN = new Planet(5.688e+26, 6.0268e7);
    public static readonly Planet URANUS = new Planet(8.686e+25, 2.5559e7);
    public static readonly Planet NEPTUNE = new Planet(1.024e+26, 2.4746e7);
    public static readonly Planet PLUTO = new Planet(1.27e+22,  1.137e6);
}

public class Planet
{
    public double Mass {get;private set;}
    public double Radius {get;private set;}

    Planet(double mass, double radius)
    {
        Mass = mass;
        Radius = radius;
    }

    // universal gravitational constant  (m3 kg-1 s-2)
    private static readonly double G = 6.67300E-11;

    public double SurfaceGravity()
    {
        return G * Mass / (Radius * Radius);
    }

    public double SurfaceWeight(double otherMass)
    {
        return otherMass * SurfaceGravity();
    }
}

Или объедините константы в класс Planet, как указано выше

person Community    schedule 22.01.2009
comment
Не совсем так - конструктор Planet должен быть приватным; Отчасти суть перечислений в том, что они представляют собой фиксированный набор значений. Значения тогда также будут определены в классе Planet. - person Jon Skeet; 22.01.2009
comment
Еще нет. 1) перечислитель отсутствует :) 2) перечисления никогда не должны изменяться. И, наконец, ваш код требует одного класса (особенно, когда у вас есть частный конструктор) - person nawfal; 08.05.2013

мы только что создали расширение enum для c # https://github.com/simonmau/enum_ext

Это просто реализация typeafeenum, но она отлично работает, поэтому мы сделали пакет, которым можно поделиться - получайте удовольствие

public sealed class Weekday : TypeSafeNameEnum<Weekday, int>
{
    public static readonly Weekday Monday = new Weekday(1, "--Monday--");
    public static readonly Weekday Tuesday = new Weekday(2, "--Tuesday--");
    public static readonly Weekday Wednesday = new Weekday(3, "--Wednesday--");
    ....

    private Weekday(int id, string name) : base(id, name)
    {
    }
}
person simonmau    schedule 02.08.2019

Вот еще одна интересная идея, которая учитывает настраиваемое поведение, доступное в Java. Я придумал следующий Enumeration базовый класс:

public abstract class Enumeration<T>
    where T : Enumeration<T>
{   
    protected static int nextOrdinal = 0;

    protected static readonly Dictionary<int, Enumeration<T>> byOrdinal = new Dictionary<int, Enumeration<T>>();
    protected static readonly Dictionary<string, Enumeration<T>> byName = new Dictionary<string, Enumeration<T>>();

    protected readonly string name;
    protected readonly int ordinal;

    protected Enumeration(string name)
        : this (name, nextOrdinal)
    {
    }

    protected Enumeration(string name, int ordinal)
    {
        this.name = name;
        this.ordinal = ordinal;
        nextOrdinal = ordinal + 1;
        byOrdinal.Add(ordinal, this);
        byName.Add(name, this);
    }

    public override string ToString()
    {
        return name;
    }

    public string Name 
    {
        get { return name; }
    }

    public static explicit operator int(Enumeration<T> obj)
    {
        return obj.ordinal;
    }

    public int Ordinal
    {
        get { return ordinal; }
    }
}

В основном у него есть параметр типа, поэтому порядковый номер будет правильно работать в разных производных перечислениях. Пример Operator Джона Скита из его ответа на другой вопрос (http://stackoverflow.com/questions/1376312/whats-the-equivalent-of-javas-enum-in-c) становится таким:

public class Operator : Enumeration<Operator>
{
    public static readonly Operator Plus = new Operator("Plus", (x, y) => x + y);
    public static readonly Operator Minus =  new Operator("Minus", (x, y) => x - y);
    public static readonly Operator Times =  new Operator("Times", (x, y) => x * y);
    public static readonly Operator Divide = new Operator("Divide", (x, y) => x / y);

    private readonly Func<int, int, int> op;

    // Prevent other top-level types from instantiating
    private Operator(string name, Func<int, int, int> op)
        :base (name)
    {
        this.op = op;
    }

    public int Execute(int left, int right)
    {
        return op(left, right);
    }
}

Это дает несколько преимуществ.

  • Порядковая поддержка
  • Преобразование в string и int, что делает операторы switch доступными
  • GetType () даст одинаковый результат для каждого значения производного типа Enumeration.
  • Статические методы из System.Enum можно добавить к базовому классу Enumeration, чтобы обеспечить ту же функциональность.
person Andrew Cooper    schedule 22.06.2012

Перечисление Java - это синтаксический сахар для представления перечислений в объектно-ориентированной манере. Это абстрактные классы, расширяющие класс Enum в Java, и каждое значение enum похоже на реализацию статического конечного общедоступного экземпляра класса enum. Посмотрите на сгенерированные классы, и для перечисления «Foo» с 10 значениями вы увидите сгенерированные классы от «Foo $ 1» до «Foo $ 10».

Я не знаю C #, но могу только предположить, что перечисление на этом языке больше похоже на традиционное перечисление в языках стиля C. Я вижу из быстрого поиска в Google, что они могут содержать несколько значений, поэтому они, вероятно, реализованы аналогичным образом, но с гораздо большими ограничениями, чем позволяет компилятор Java.

person JeeBee    schedule 22.01.2009
comment
Ну разве все в Java и C # не связано с синтаксическим сахаром для байт-кодов JVM или CLR? :) Просто говорю' - person thenonhacker; 25.05.2011

Перечисления Java позволяют легко преобразовывать типы из имени с помощью генерируемого компилятором метода valueOf, т. Е.

// Java Enum has generics smarts and allows this
Planet p = Planet.valueOf("MERCURY");

Эквивалент для необработанного перечисления в C # более подробен:

// C# enum - bit of hoop jumping required
Planet p = (Planet)Enum.Parse(typeof(Planet), "MERCURY");

Однако, если вы пойдете по маршруту, предложенному Кентом, вы можете легко реализовать метод ValueOf в своем классе перечисления.

person serg10    schedule 23.01.2009
comment
В примере Java используется синтетический метод, сгенерированный компилятором - никакого отношения к дженерикам. Enum имеет общий метод valueOf, но он использует универсальные шаблоны Class, а не Enum. - person Tom Hawtin - tackline; 05.04.2009

Я подозреваю, что перечисления в C # - это просто константы, внутренние по отношению к CLR, но не знакомые с ними. Я декомпилировал некоторые классы на Java и могу сказать, что вы хотите, чтобы Enums были после преобразования.

Java делает что-то подлое. Он рассматривает класс enum как обычный класс с, насколько я могу понять, использованием большого количества макросов при обращении к значениям перечисления. Если у вас есть оператор case в классе Java, который использует перечисления, он заменяет ссылки перечисления на целые числа. Если вам нужно перейти к строке, он создает массив строк, индексированных порядковым номером, который он использует в каждом классе. Подозреваю, что сэкономлю на боксе.

Если вы загрузите этот декомпилятор, вы увидите, как он создает свой класс и интегрирует его. Честно говоря, довольно увлекательно. Раньше я не использовал класс enum, потому что думал, что он раздут только для массива констант. Мне это нравится больше, чем ограниченный способ их использования на C #.

http://members.fortunecity.com/neshkov/dj.html - декомпилятор Java

person Paul Bruner    schedule 04.06.2011

Перечисление в Java намного сложнее, чем перечисление C #, и, следовательно, более мощное. Поскольку это просто еще один синтаксический сахар времени компиляции, мне интересно, действительно ли стоит включать язык, учитывая его ограниченное использование в реальных приложениях. Иногда сложнее убрать что-то из языка, чем отказаться от давления и включить незначительную функцию.

person dmihailescu    schedule 19.01.2011
comment
Я с уважением не согласен. Перечисления Java 1.5 - это мощная языковая функция, которую я использовал много раз для реализации более объектно-ориентированного решения проблемы, связанной с дискретным набором константных именованных элементов. - person Ogre Psalm33; 25.05.2011
comment
Может быть, вы это сделали. Но помимо интеллектуальной языковой интеграции «переключателя», остальные функции могут быть легко воспроизведены на C # или самой Java, как показано в приведенных выше примерах. - person dmihailescu; 04.06.2011
comment
@dmihailescu Тогда enum в Java намного сложнее, чем C #. Так что я бросил Java ... - person Mukus; 12.02.2015

Вы также можете использовать служебный класс для каждого типа перечисления, который содержит экземпляр с расширенными данными для каждого значения перечисления.

public enum Planet
{
    MERCURY,
    VENUS
}

public class PlanetUtil
{
    private static readonly IDictionary<Planet, PlanetUtil> PLANETS = new Dictionary<Planet, PlanetUtil();

    static PlanetUtil()
    {
        PlanetUtil.PLANETS.Add(Planet.MERCURY, new PlanetUtil(3.303e+23, 2.4397e6));
        PlanetUtil.PLANETS.Add(Planet.VENUS, new PlanetUtil(4.869e+24, 6.0518e6));
    }

    public static PlanetUtil GetUtil(Planet planet)
    {
        return PlanetUtil.PLANETS[planet];
    }

    private readonly double radius;
    private readonly double mass;

    public PlanetUtil(double radius, double mass)
    {
        this.radius = radius;
        this.mass = mass;
    }

    // getter
}
person djmj    schedule 28.08.2015