C # против Java Enum (для новичков в C #) - PullRequest
166 голосов
/ 22 января 2009

Я некоторое время программировал на 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 ...]

Ответы [ 12 ]

207 голосов
/ 24 января 2011

В 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
}
201 голосов
/ 22 января 2009

Перечисления в 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;
    }
}
34 голосов
/ 13 сентября 2009

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

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

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

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

12 голосов
/ 22 января 2009

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

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

4 голосов
/ 22 января 2009

Как-то так я думаю:

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, как указано выше

3 голосов
/ 22 июня 2012

Вот еще одна интересная идея, которая учитывает пользовательское поведение, доступное в 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, что делает операторы переключения осуществимыми
  • GetType () выдаст одинаковый результат для каждого из значений производного типа перечисления.
  • Статические методы из System.Enum могут быть добавлены к базовому классу перечисления, чтобы обеспечить те же функции.
2 голосов
/ 04 июня 2011

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

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

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

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

2 голосов
/ 23 января 2009

Перечисления 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 в своем классе enum.

2 голосов
/ 22 января 2009

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

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

0 голосов
/ 29 августа 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
}
...