简体   繁体   English

C#vs Java Enum(适用于C#的新手)

[英]C# vs Java Enum (for those new to C#)

I've been programming in Java for a while and just got thrown onto a project that's written entirely in C#. 我已经用Java编程了一段时间,只是投入了一个完全用C#编写的项目。 I'm trying to come up to speed in C#, and noticed enums used in several places in my new project, but at first glance, C#'s enums seem to be more simplistic than the Java 1.5+ implementation. 我正试图加快C#的速度,并注意到在我的新项目中几个地方使用的枚举,但乍一看,C#的枚举似乎比Java 1.5+实现更简单。 Can anyone enumerate the differences between C# and Java enums, and how to overcome the differences? 任何人都可以枚举C#和Java枚举之间的差异,以及如何克服这些差异? (I don't want to start a language flame war, I just want to know how to do some things in C# that I used to do in Java). (我不想开始语言火焰战,我只想知道如何在C#中做一些我以前用Java做的事情)。 For example, could someone post a C# counterpart to Sun's famous Planet enum example? 例如,有人可以发布一个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 ...]

In C# you can define extension methods on enums, and this makes up for some of the missing functionality. 在C#中,您可以在枚举上定义扩展方法 ,这可以弥补一些缺少的功能。

You can define Planet as an enum and also have extension methods equivalent to surfaceGravity() and surfaceWeight() . 您可以将Planet定义为枚举,并且还具有与surfaceGravity()surfaceWeight()等效的扩展方法。

I have used custom attributes as suggested by Mikhail , but the same could be achieved using a Dictionary. 我使用了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
}

Enumerations in the CLR are simply named constants. CLR中的枚举只是命名常量。 The underlying type must be integral. 基础类型必须是整数。 In Java an enumeration is more like a named instance of a type. 在Java中,枚举更像是类型的命名实例。 That type can be quite complex and - as your example shows - contain multiple fields of various types. 该类型可能非常复杂,并且 - 如您的示例所示 - 包含多个不同类型的字段。

To port the example to C# I would just change the enum to an immutable class and expose static readonly instances of that class: 要将示例移植到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;
    }
}

In C# attributes can be used with enums. 在C#中,属性可以与枚举一起使用。 Good example of this programming pattern with detailed description is here (Codeproject) 这里有详细描述的编程模式的好例子(Codeproject)

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

Edit: this question has been recently asked again and answered by Jon Skeet: What's the equivalent of Java's enum in C#? 编辑:最近又问过这个问题并由Jon Skeet回答: 在C#中,Java的枚举是什么? Private inner classes in C# - why aren't they used more often? C#中的私有内部类 - 为什么不经常使用它们?

Edit 2: see the accepted answer which extends this approach in a very brilliant way! 编辑2:看到接受的答案 ,以非常出色的方式扩展这种方法!

Java enums are actually full classes which can have a private constructor and methods etc, whereas C# enums are just named integers. Java枚举实际上是完整的类,可以有私有构造函数和方法等,而C#枚举只是命名为整数。 IMO Java's implementation is far superior. IMO Java的实现远非优越。

This page should help you a lot while learning c# coming from a java camp. 在学习来自java阵营的c#时,这个页面可以帮助你很多。 (The link points to the differences about enums (scroll up / down for other things) (该链接指向枚举的差异(向上/向下滚动其他内容)

Something like this I think: 我认为这样的事情:

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

Or combine the constants into the Planet class as above 或者将常量组合到Planet类中,如上所述

Here's another interesting idea which caters for the custom behaviour available in Java. 这是另一个有趣的想法,它迎合了Java中可用的自定义行为。 I came up with the following Enumeration base class: 我想出了以下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; }
    }
}

It's got a type parameter basically just so the ordinal count will work properly across different derived enumerations. 它基本上只有一个类型参数,所以序数可以在不同的派生枚举中正常工作。 Jon Skeet's Operator example from his answer to another question (http://stackoverflow.com/questions/1376312/whats-the-equivalent-of-javas-enum-in-c) above then becomes: 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);
    }
}

This gives a few advantages. 这提供了一些优点。

  • Ordinal support 顺序支持
  • Conversion to string and int which makes switch statements feasible 转换为stringint ,使switch语句可行
  • GetType() will give the same result for each of the values of a derived Enumeration type. GetType()将为派生的Enumeration类型的每个值提供相同的结果。
  • The Static methods from System.Enum can be added to the base Enumeration class to allow the same functionality. System.Enum的Static方法可以添加到基本Enumeration类中以允许相同的功能。

A Java enum is syntactic sugar to present enumerations in an OO manner. Java枚举是一种语法糖,用于以OO方式呈现枚举。 They're abstract classes extending the Enum class in Java, and each enum value is like a static final public instance implementation of the enum class. 它们是在Java中扩展Enum类的抽象类,每个枚举值类似于枚举类的静态最终公共实例实现。 Look at the generated classes, and for an enum "Foo" with 10 values, you'll see "Foo$1" through "Foo$10" classes generated. 查看生成的类,对于具有10个值的枚举“Foo”,您将看到“Foo $ 1”到“Foo $ 10”类生成。

I don't know C# though, I can only speculate that an enum in that language is more like a traditional enum in C style languages. 我不知道C#,我只能推测该语言中的枚举更像是C风格语言中的传统枚举。 I see from a quick Google search that they can hold multiple values however, so they are probably implemented in a similar manner, but with far more restrictions than what the Java compiler allows. 我从快速谷歌搜索中看到它们可以保存多个值,因此它们可能以类似的方式实现,但具有比Java编译器允许的更多限制。

Java enums allow easy typesafe conversions from the name using the compiler-generated valueOf method, ie Java枚举允许使用编译器生成的valueOf方法从名称中轻松进行类型安全转换,即

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

The equivalent for a raw enum in C# is more verbose: C#中的原始枚举的等价物更详细:

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

However, if you go down the route sugegsted by Kent, you can easily implement a ValueOf method in your enum class. 但是,如果沿着Kent的路线走下去,您可以在枚举类中轻松实现ValueOf方法。

we have just made an enum extension for c# https://github.com/simonmau/enum_ext 我们刚刚为c# https://github.com/simonmau/enum_ext制作了一个枚举扩展

It's just a implementation for the typesafeenum, but it works great so we made a package to share - have fun with it 它只是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)
    {
    }
}

I suspect enums in C# are just constants internal to the CLR, but not that familiar with them. 我怀疑C#中的枚举只是CLR内部的常量,但不熟悉它们。 I have decompiled some classes in Java and I can tell you want Enums are once you convert. 我已经用Java反编译了一些类,我可以告诉你一旦你转换就想要枚举。

Java does something sneaky. Java做鬼鬼祟祟的事情。 It treats the enum class as aa normal class with, as close as I can figure, using lots of macros when referencing the enum values. 它将枚举类视为一个普通的类,尽可能接近我在引用枚举值时使用大量的宏。 If you have a case statement in a Java class that uses enums, it replaces the enum references to integers. 如果在使用枚举的Java类中有case语句,则它会将枚举引用替换为整数。 If you need to go to string, it creates an array of strings indexed by an ordinal that it uses in each class. 如果你需要转到字符串,它会创建一个由在每个类中使用的序数索引的字符串数组。 I suspect to save on boxing. 我怀疑要节省拳击。

If you download this decompiler you will get to see how it creates its class an integrates it. 如果您下载此反编译器,您将看到它如何创建它的类并进行集成。 Rather fascinating to be honest. 老实说相当有趣。 I used to not use the enum class because I thought it was to bloated for just an array of constants. 我曾经不使用枚举类,因为我认为只是一个常量数组才会膨胀。 I like it better than the limited way you can use them in C#. 我比在C#中使用它们的有限方式更喜欢它。

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

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

You could also use a utility class for each enum type which holds a instance with advanced data for each enum value. 您还可以为每个枚举类型使用实用程序类,该类型包含具有每个枚举值的高级数据的实例。

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
}

The enum in Java is much more complex than C# enum and hence more powerful. Java中的枚举比C#enum复杂得多,因此更强大。 Since it is just another compile time syntactical sugar I'm wondering if it was really worth having included the language given its limited usage in real life applications. 因为它只是另一个编译时间的语法糖,我想知道是否真的值得包含语言,因为它在现实生活中的应用有限。 Sometimes it's harder keeping stuff out of the language than giving up to the pressure to include a minor feature. 有时,除了放弃包含次要功能的压力之外,更难以保持语言的语言。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM