C#vs Java枚举(适用于C#新手)


182

我已经用Java编程了一段时间,只是被扔到一个完全用C#编写的项目中。我试图加快C#的速度,并注意到在我的新项目中的多个地方都使用了枚举,但是乍一看,C#的枚举似乎比Java 1.5+实现更简单。谁能列举C#和Java枚举之间的差异,以及如何克服这些差异?(我不想发动语言大战,我只是想知道如何用Java来做C#中的某些事情)。例如,有人可以在Sun著名的Planet枚举示例中发布C#副本吗?

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 ...]

1
@ycomp我不能为此而功劳。它来自Sun(现在是Oracle):docs.oracle.com/javase/tutorial/java/javaOO/enum.html
Ogre Psalm33

Answers:


210

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

4
这种穷人必须被迫使用Java 1.4及以下版本的穷人必须实现的这种类型安全的枚举。Java5的枚举也许是Java 5+的最佳功能,特别是因为它们可以在switch语句中使用。
MetroidFan2002

9
@Chris:只有标志枚举应该是多元的。即,使用|组合成员的枚举。操作员。
肯特·布加阿特

5
@Mladen:这完全取决于上下文。枚举的行星可能非常适合提供访问有限数量的行星的游戏。如果将新星球添加到游戏中,则更改代码可能正是您想要的。
肯特·布加阿特

3
@Richie_W您可以使用Values属性遍历枚举。
乔纳森(Jonathan)2010年

23
哇...在C#中比在Java中以更详细的方式实现的东西几乎令人难以置信
Sune Rasmussen 2012年

218

在C#中,您可以在枚举上定义扩展方法,这弥补了一些缺少的功能。

您可以定义Planet为枚举,也可以具有与surfaceGravity()和等效的扩展方法surfaceWeight()

我使用了Mikhail建议的自定义属性,但是使用Dictionary可以实现相同的目的。

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
}

20
我认为应该对此进行更多表决。它更接近Java枚举的工作方式。我可以做一些类似Planet.MERCURY.GetSurfaceGravity()的操作-注意Enum的扩展方法!
thenonhacker 2011年

2
绝对可以。Enums上的扩展方法(heck,通常是扩展方法)是C#的重要补充。
KeithS 2011年

3
@AllonGuralnek谢谢。尽管并非所有人都同意元数据。请参阅MattDavey对一个相关的codereview.stackexchange问​​题的评论。
finnw

@finnw:我从未听说过Code Review SE网站-谢谢!我在这里继续进行讨论(尽管在程序员那里可能值得提出自己的问题)。
Allon Guralnek,2011年

这是一个很棒的解决方案-有没有人测试过它的性能?例如,与访问类的属性相比,访问该属性需要多长时间。
西蒙·迈耶

35

在C#中,属性可以与枚举一起使用。有详细的描述这种编程模式的很好的例子就是在这里(CodeProject上)

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

编辑:最近又有人问过这个问题,乔恩·斯凯特(Jon Skeet)回答了:C#中Java枚举的含义是什么? C#中的私有内部类-为什么它们不经常使用?

编辑2:看到公认的答案,它以一种非常出色的方式扩展了这种方法!


1
真好!这仅感觉有点笨拙,但是在向枚举添加额外数据的情况下,这是非常可接受的方法。坦白地说,有人花了这么长时间提到了这个出色的解决方案,我感到非常惊讶!
食人魔赞美诗09年

13

Java枚举实际上是完整类,可以具有私有构造函数和方法等,而C#枚举只是命名为整数。IMO Java的实现要好得多。

在学习来自Java阵营的c#时,此页面应该对您有很大帮助。(链接指向有关枚举的差异(为其他目的向上/向下滚动)


1
尽管您的链接对C#和Java之间的异同进行了有趣而详尽的概述,但文本中存在很多错误(例如,错误地指出Java保护等于C#内部的,而Java应该受到内部的保护)。所以,不要把一切都
当作

1
我不会说Java枚举优越,即使我是Java的粉丝。C#支持简单的整数声明,例如FOO = 0在ORM工具中更易于使用(不需要容易出错的ordinal()用法)。进一步的C#支持按位枚举,这通常非常有用,尤其是与结合使用时EntityFramework。Java应该扩展其枚举,以允许它们也绑定到整数。那么他们会更好:)
djmj

4

我认为是这样的:

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上面的类中


8
不完全是-Planet构造函数应该是私有的;枚举的部分原因是它们是一组固定的值。这些值也将在Planet类中定义。
乔恩·斯基特

还没。1)缺少枚举器:) 2)枚举永远不应是可变的。哦,最后,您的代码乞求一个单一的类(尤其是当您拥有私有构造函数时)
nawfal 2013年

3

这是另一个有趣的想法,可以满足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; }
    }
}

它基本上具有类型参数,因此序数将在不同的派生枚举之间正常工作。乔恩·斯凯特(Jon Skeet)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);
    }
}

这提供了一些优点。

  • 顺序支持
  • 转换到stringint这使得开关语句可行
  • 对于派生的枚举类型的每个值,GetType()将给出相同的结果。
  • 可以将Static方法从System.Enum添加到基本Enumeration类中,以允许相同的功能。

3

我们刚刚为C#作了一个枚举扩展 https://github.com/simonmau/enum_ext

它只是typesafeenum的一个实现,但是效果很好,所以我们制作了一个共享的包-乐在其中

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)
    {
    }
}

2

Java枚举是用于以OO方式呈现枚举的语法糖。它们是在Java中扩展Enum类的抽象类,每个枚举值都像枚举类的静态最终公共实例实现。查看生成的类,对于包含10个值的枚举“ Foo”,您将看到生成的“ Foo $ 1”到“ Foo $ 10”类。

虽然我不了解C#,但我只能推测该语言中的枚举更像是C风格语言中的传统枚举。我从Google的快速搜索中看到它们可以容纳多个值,因此它们可能以类似的方式实现,但比Java编译器所允许的限制要多得多。


3
难道Java和C#中的所有内容都不都是关于JVM或CLR字节码的语法糖吗?:)只是说
thenonhacker

2

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

但是,如果您走了肯特(Kent)建议的路线,则可以轻松地ValueOf在枚举类中实现一个方法。


Java示例使用的是编译器生成的综合方法-与泛型完全无关。枚举确实具有泛型的valueOf方法,但使用的是Class的泛型,而不是Enum。
Tom Hawtin-抢险活动

2

我怀疑C#中的枚举只是CLR内部的常量,但对它们并不熟悉。我已经用Java反编译了一些类,我可以告诉您,一旦转换后就希望枚举。

Java做了一些偷偷摸摸的事情。我将枚举类视为普通类,并且在引用枚举值时会使用很多宏,正如我所知。如果您在使用枚举的Java类中有一个case语句,它将替换枚举对整数的引用。如果需要转到字符串,它将创建一个字符串数组,该数组由它在每个类中使用的序数索引。我怀疑可以节省拳击费用。

如果下载此反编译器,您将看到它如何创建其类并对其进行集成。说实话,这很让人着迷。我以前不使用枚举类,因为我认为它只是为了常量数组而肿。与在C#中使用它们的有限方式相比,我更喜欢它。

http://members.fortunecity.com/neshkov/dj.html-Java反编译器


0

Java中的枚举比C#枚举复杂得多,因此功能更强大。由于这只是另一种编译时语法糖,我想知道考虑到它在现实应用中的有限使用,是否值得包含该语言。有时候,比放弃包含次要功能的压力要难得多。


2
我谨不同意。Java 1.5枚举是一项强大的语言功能,我已多次使用它来实现一个以面向对象为中心的解决方案,以解决一组离散的常量命名项问题。
Ogre Psalm33,2011年

1
可能是你做的。但是,除了智能的“切换”语言集成之外,如上面的示例所示,其余功能也可以轻松地用C#或Java本身复制。
dmihailescu 2011年

@dmihailescu“然后用Java枚举比C#复杂得多。所以我放弃了Java ...”
Mukus 2015年

0
//Review the sample enum below for a template on how to implement a JavaEnum.
//There is also an EnumSet implementation below.

public abstract class JavaEnum : IComparable {
    public static IEnumerable<JavaEnum> Values {
        get {
            throw new NotImplementedException("Enumeration missing");
        }
    }

    public readonly string Name;

    public JavaEnum(string name) {
        this.Name = name;
    }

    public override string ToString() {
        return base.ToString() + "." + Name.ToUpper();
    }

    public int CompareTo(object obj) {
        if(obj is JavaEnum) {
            return string.Compare(this.Name, ((JavaEnum)obj).Name);
        } else {
            throw new ArgumentException();
        }
    }


    //Dictionary values are of type SortedSet<T>
    private static Dictionary<Type, object> enumDictionary;
    public static SortedSet<T> RetrieveEnumValues<T>() where T : JavaEnum {
        if(enumDictionary == null) {
            enumDictionary = new Dictionary<Type, object>();
        }
        object enums;
        if(!enumDictionary.TryGetValue(typeof(T), out enums)) {
            enums = new SortedSet<T>();
            FieldInfo[] myFieldInfo = typeof(T).GetFields(BindingFlags.Static | BindingFlags.DeclaredOnly | BindingFlags.Public);
            foreach(FieldInfo f in myFieldInfo) {
                if(f.FieldType == typeof(T)) {
                    ((SortedSet<T>)enums).Add((T)f.GetValue(null));
                }
            }
            enumDictionary.Add(typeof(T), enums);
        }
        return (SortedSet<T>)enums;
    }
}


//Sample JavaEnum
public class SampleEnum : JavaEnum {
    //Enum values
    public static readonly SampleEnum A = new SampleEnum("A", 1);
    public static readonly SampleEnum B = new SampleEnum("B", 2);
    public static readonly SampleEnum C = new SampleEnum("C", 3);

    //Variables or Properties common to all enums of this type
    public int int1;
    public static int int2 = 4;
    public static readonly int int3 = 9;

    //The Values property must be replaced with a call to JavaEnum.generateEnumValues<MyEnumType>() to generate an IEnumerable set.
    public static new IEnumerable<SampleEnum> Values {
        get {
            foreach(var e in JavaEnum.RetrieveEnumValues<SampleEnum>()) {
                yield return e;
            }
            //If this enum should compose several enums, add them here
            //foreach(var e in ChildSampleEnum.Values) {
            //    yield return e;
            //}
        }
    }

    public SampleEnum(string name, int int1)
        : base(name) {
        this.int1 = int1;
    }
}


public class EnumSet<T> : SortedSet<T> where T : JavaEnum {
    // Creates an enum set containing all of the elements in the specified element type.
    public static EnumSet<T> AllOf(IEnumerable<T> values) {
        EnumSet<T> returnSet = new EnumSet<T>();
        foreach(T item in values) {
            returnSet.Add(item);
        }
        return returnSet;
    }

    // Creates an enum set with the same element type as the specified enum set, initially containing all the elements of this type that are not contained in the specified set.
    public static EnumSet<T> ComplementOf(IEnumerable<T> values, EnumSet<T> set) {
        EnumSet<T> returnSet = new EnumSet<T>();
        foreach(T item in values) {
            if(!set.Contains(item)) {
                returnSet.Add(item);
            }
        }
        return returnSet;
    }

    // Creates an enum set initially containing all of the elements in the range defined by the two specified endpoints.
    public static EnumSet<T> Range(IEnumerable<T> values, T from, T to) {
        EnumSet<T> returnSet = new EnumSet<T>();
        if(from == to) {
            returnSet.Add(from);
            return returnSet;
        }
        bool isFrom = false;
        foreach(T item in values) {
            if(isFrom) {
                returnSet.Add(item);
                if(item == to) {
                    return returnSet;
                }
            } else if(item == from) {
                isFrom = true;
                returnSet.Add(item);
            }
        }
        throw new ArgumentException();
    }

    // Creates an enum set initially containing the specified element(s).
    public static EnumSet<T> Of(params T[] setItems) {
        EnumSet<T> returnSet = new EnumSet<T>();
        foreach(T item in setItems) {
            returnSet.Add(item);
        }
        return returnSet;
    }

    // Creates an empty enum set with the specified element type.
    public static EnumSet<T> NoneOf() {
        return new EnumSet<T>();
    }

    // Returns a copy of the set passed in.
    public static EnumSet<T> CopyOf(EnumSet<T> set) {
        EnumSet<T> returnSet = new EnumSet<T>();
        returnSet.Add(set);
        return returnSet;
    }

    // Adds a set to an existing set.
    public void Add(EnumSet<T> enumSet) {
        foreach(T item in enumSet) {
            this.Add(item);
        }
    }

    // Removes a set from an existing set.
    public void Remove(EnumSet<T> enumSet) {
        foreach(T item in enumSet) {
            this.Remove(item);
        }
    }
}

0

您还可以为每种枚举类型使用一个实用工具类,该类包含一个包含每个枚举值的高级数据的实例。

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
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.