C#中与Java的枚举相当的是什么?

27

C#中与Java的枚举相对应的是什么?


2
也许我想太多了,但我认为你已经知道了C#的枚举类型,并且你所提到的Java的枚举类型也有一些类似的功能。如果是这样的话,或许你可以在问题中详细说明一下。 - John MacIntyre
9
如果您知道Java中的“enum”相关内容,那么这个问题就非常清晰明了。 - Pavel Minaev
@Yishai - 在C#中,枚举默认为Int32,但可以更改为任何数字类型。 - Richard Szalay
2
同样的问题:https://dev59.com/NnRB5IYBdhLWcg3w6bYE - Mikhail Poda
6个回答

39

在C#中并没有完全实现Java枚举的功能。但是,您可以通过使用嵌套类型和私有构造函数来接近这个功能。例如:

using System;
using System.Collections.Generic;
using System.Xml.Linq;

public abstract class Operator
{
    public static readonly Operator Plus = new PlusOperator();
    public static readonly Operator Minus = 
         new GenericOperator((x, y) => x - y);
    public static readonly Operator Times = 
         new GenericOperator((x, y) => x * y);
    public static readonly Operator Divide = 
         new GenericOperator((x, y) => x / y);

    // Prevent other top-level types from instantiating
    private Operator()
    {
    }

    public abstract int Execute(int left, int right);

    private class PlusOperator : Operator
    {
        public override int Execute(int left, int right)
        {
            return left + right;
        }
    }

    private class GenericOperator : Operator
    {
        private readonly Func<int, int, int> op;

        internal GenericOperator(Func<int, int, int> op)
        {
            this.op = op;
        }

        public override int Execute(int left, int right)
        {
            return op(left, right);
        }
    }
}
当然你不必使用嵌套类型,但它们提供了 Java 枚举所擅长的"自定义行为"部分。在其他情况下,您可以通过将参数传递给私有构造函数来获取一个众所周知的受限值集。

这里有一些它无法提供的东西:

  • 序数支持
  • Switch 支持
  • EnumSet
  • (作为单例的)序列化/反序列化

虽然有些需要努力才能实现,但其中一些可能是可行的;尽管 Switch 没有黑科技不太现实。如果语言像这样做了一些事情,它可以通过自动进行黑科技来使 Switch 正常工作(例如,声明许多 const 字段并将任何枚举类型的 switch 更改为整数 switch,只允许“已知”情况。)

哦,而局部类型意味着您不必在同一个文件中拥有 所有 枚举值。如果每个值变得非常复杂(这绝对是可能的),每个值都可以有自己的文件。


我认为通过使用隐式转换为int来支持switch语句(和序数)是微不足道的,对吗?我忽略了什么? - lmat - Reinstate Monica
2
@LimitedAtonement:你会如何表示你需要用于情况表达式的常量值? - Jon Skeet
我目前在VS2012中复制/粘贴此代码时遇到了构建错误。我正在研究一系列评估器的模式,涵盖多个对象类型。“不兼容的匿名函数签名。”我对匿名函数签名的理解一直很薄弱,但在我看来它是正确的。 - Jonathan
找到了。private class GenericOperator : Operator { private readonly Func op; internal GenericOperator(Func op) { this.op = op; } public override int Execute(int left, int right) { return op(left, right); } } - Jonathan
@Jonathan:已修复,谢谢。 - Jon Skeet
显示剩余3条评论

21

枚举类型是在Java中比C#更好实现的几个语言特性之一。

在Java中,枚举类型是完整的具名实例,并且属于一种类型,而C#枚举类型基本上是具有名称的常量。

尽管在基本情况下它们看起来相似,但是在Java中,您拥有更多的控制力,因为您可以向每个枚举添加行为,因为它们是完整的类。

您是否特别寻找某些功能?


8
"它们将起到相同的作用" - 在我看来并不是,因为常数无法具有自定义的行为,就像Java中的枚举值一样。 - Jon Skeet
2
是的,因此有“基本使用”的限定词。 我同意Java枚举的一个好处是自定义行为。 - Chi
2
我喜欢Java的枚举类型胜过C#的枚举类型。使用C#的枚举类型时,你会感觉到有很多Switch语句的味道,而且这些散布在Switch语句中的代码可以作为与每个枚举类型相关联的方法来放置。 - user46915

6

这里有一个有趣的想法。我设计了以下枚举基类:

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上面的Operator示例变成:

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,使得switch语句成为可能
  • 对于派生的枚举类型的每个值,GetType()都会给出相同的结果。
  • 可以将System.Enum中的静态方法添加到基本枚举类中,以允许相同的功能。

我正在进行一些Java到C#的转换,所以一个月前我看了你的答案并认为“对我的需求来说有点麻烦”...现在我又回来了,发现它更有价值了,因为我需要“按(字符串)名称检索枚举”的功能。赞! - Cristian Diaconescu
@Andrew Cooper,关于“这样做有一些优点。序数支持转换为字符串和整数,使得switch语句可行” - 看起来这仍然不支持switch语句,因为编译器抱怨case语句需要一个常量值。它既不接受(int)强制转换,也不接受.Ordinal值作为const。 - tomosius

5
您可以使用旧的类型安全枚举模式,这种模式在我们获得真正的枚举之前在Java中使用(假设C#中的枚举不是类,如评论所述)。该模式在此页面的中间部分之前进行描述。

2
//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

枚举,或者你需要Java枚举拥有但C#没有的特定功能吗?


在Java中,枚举可以为每个枚举具有多个关联值、带有逻辑的方法和构造函数。至少在C# 3.0中,C#枚举仍然只是一个常量列表,可能有关联值。 - Ogre Psalm33
7
Java的枚举类型比C#的更加强大。 - Kramii
+1 给计数器,抵消 Kramii 的 -1。C# 枚举可以有扩展方法,这弥补了一些功能的缺失。 - finnw

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接