是否可能创建一个包含数组的枚举?

5
我想声明几个常量对象,每个对象有两个子对象,并希望将它们存储在一个枚举中以进行组织。在 C# 中是否可以像这样做?
enum Car
{
  carA = { 'ford', 'red' }
  carB = { 'bmw', 'black' }
  carC = { 'toyota', 'white' }
}
8个回答

4
不可以,C#语言不支持这样的操作。
你可以创建一个
Dictionary<Car, List<string>> cars;

您需要向其添加条目,例如:
cars = new Dictionary<Car, List<String>>();
cars.Add(Car.carA, new List<String>() { "ford", "red" });

请注意,如果您正在混淆“福特”和“红色”的概念,您可能需要考虑创建一个代表该事物的对象,例如:
public class CarDetails
{
    public string Maker { get; set; }
    public string Color { get; set; }
}

那么,你的Dictionary对象将会是这样的。
Dictionary<Car, CarDetails> cars;
cars = new Dictionary<Car.carA, CarDetails>();

cars.Add(Car.carA, new CarDetails() { Maker = "ford", Color = "red" });

1
如果这个类旨在表示静态组合枚举,你不认为以下问题会带来麻烦吗:(1) 字典条目是可变的,每辆车都可以随时被删除;(2) 每辆车的属性也是可变的,所以一辆车可以被改变;(3) 很容易创建CarDetails实例,因此引用相等将不足以判断;(4) 两个具有相同值的CarDetails对象将不被视为相等。 - smartcaveman
@phoog,第3点指出引用相等性不是足够的相等性测试(请查看我的答案,在其中我使用了私有构造函数进行引用相等性)。然而,由于反序列化,我仍然添加了值相等性重写。第4点指出,值相等性将失败,因为在此实现中没有Equals()GetHashCode()重写,所以:new CarDetails { Maker = "ford", Color = "red" }.Equals(new CarDetails { Maker = "ford", Color = "red" })将返回false。 - smartcaveman
@phoog,更简单地说,使用此实现方式(3)无法依赖于==ReferenceEquals()。(4) 我们不能依赖于Equals()GetHashCode() - smartcaveman
@smartcaveman,在我看来,反序列化链接表明您绝不能依赖引用相等性来替代值相等性。这使得第3点或多或少变得微不足道,因为始终可以创建其他实例,无论是否容易。 - phoog
@EricJ.,(1)OP 开始说“我想声明几个常量对象”。(2)即使你错过了这一点,枚举是不可变的,OP 正在询问一个枚举。 - smartcaveman
显示剩余2条评论

3

不,这是不可能的。您可以定义一个静态类。

public static class Car
{
    public static readonly ReadOnlyCollection<string> carA = Array.AsReadOnly(new[]{"ford","red"});
    public static readonly ReadOnlyCollection<string> carB = Array.AsReadOnly(new[]{"bmw","black"});
    public static readonly ReadOnlyCollection<string> carC = Array.AsReadOnly(new[]{"toyota","white"});
}

为了保留枚举的不可变性属性,我使用了ReadOnlyCollection<string>而不是string[]

这不满足每个Car的属性都是Car实例的条件。您可以进一步使用自定义枚举类来获得所需的结果,该类具有私有构造函数和静态实例。Jimmy Bogard在http://lostechies.com/jimmybogard/2008/08/12/enumeration-classes/上提供了一个示例实现和基类。他提供了一个可扩展的基类,如果您经常需要做这件事,应该研究一下。然而,只是为了让您了解这个想法,使用此方法与您的数据的简单实现如下:

 public sealed class Car  : IEquatable<Car> { 

 // declare and define each of your constants

    public static readonly Car carA = new Car("ford", "red");

    public static readonly Car carB = new Car("bmw", "black");

    public static readonly Car carC = new Car("toyota", "white");


 //  define an instance-scoped value object to hold your subObjects
     private readonly Tuple<string,string> subObjects;

 // a private constructor ensures that all your instances will be constant 
 // and will be defined from within Car
     private Car(string make, string color){
     // require valid sub objects
     if(string.IsNullOrEmpty(make))throw new ArgumentException("Invalid Make","make");
     if(string.IsNullOrEmpty(color))throw new ArgumentException("Invalid Color","color");

     // create a subObjects tuple to hold your values to simplify value comparison
     this.subObjects = Tuple.Create(make,color);
     }

 //  declare public accessors for your 
     public string Make { get { return this.subObjects.Item1; } }
     public string Color { get { return this.subObjects.Item2; } }

 // override Equality for value equality, and resulting consistency across AppDomains
         public override bool Equals(object obj){ return obj is Car && this.Equals((Car)obj); }
     public bool Equals(Car otherCar){ return otherCar != null && this.subObjects.Equals(otherCar.subObjects); }
     public override int GetHashCode(){ return this.subObjects.GetHashCode(); }
         public static bool operator ==(Car left, Car right){ return left == null ? right == null : left.Equals(right); }
         public static bool operator !=(Car left, Car right){ return !(left == right); }

 // override ToString() to provide view of values 
     public override string ToString(){ return string.Format("Car({0},{1})",Make,Color); }
}

现在,您可以像使用enum一样访问它。例如:

 void Main(){
     var blackBeamer = Car.carC;
     Console.WriteLine("carC is a " + blackBeamer.Color +" " + blackBeamer.Make);
 }

+1. 对于如何创建良好的类似枚举的集合有很多好的建议。 - Alexei Levenkov

2

首先,C#中的枚举实际上是整数值,而不是字符串。

其次,枚举中的每个值只能有一个唯一的值。

但你可以为每个枚举值指定整数值,这样允许枚举中的多个元素拥有相同的整数值:

public enum Car
{
    Ford = 1,
    Red = 1,
    Bmw = 2,
    Black = 2
    // etc.
}

但是听起来你真正需要的是一个词典。

很聪明。不过我认为使用字典存在一些问题,可以看看我在Eric J的帖子中的评论。 - smartcaveman

1
枚举的值始终由整数表示。您不能使用不同类型(如字符串数组)。
您可以执行以下操作以获得类似的结果:
Dictionary<Car, string[]> cars;
cars = new Dictionary<Car, string[]>();
cars.Add(Car.carA, new string[]{"ford", "red"});
cars.Add(Car.carB, new string[]{"bmw", "black"});
cars.Add(Car.carC, new string[]{"toyota", "white"});

然而,只有在您需要将枚举映射到字符串时才应这样做。您似乎混淆了各种“事物”,即汽车的品牌和颜色。您应该考虑更像这样的东西:

enum Make {
    Ford,
    BMW,
    Toyota
}

enum Color {
    Red,
    Black,
    White
}

并将汽车表示为:

struct Car {
    Make make;
    Color color;
    public Car(Make m, Color c) { make = m; color = c; }
}

并将列表列出:

Car[] cars = new Car[]{new Car(Make.Ford, Color.Red), new Car(Make.BMW, Make.Black), new Car(Make.Toyota, Make.White)};

1
你好,在你的字典定义中,你使用了List<string>,但是在声明新字典时却使用了string[]...这是打错字了吗? - dukenukem
-1,这段代码无法工作/编译。你没有在字段上定义构造函数或公共访问器。此外,cars数组是可变的,因此任何值都可能被覆盖。 - smartcaveman
我建议另一种做事的方式,但我无法确定原帖作者是否需要不可变的东西。 - CrazyCasta
@CrazyCasta,枚举是不可变的。 - smartcaveman
@CrazyCasta "我想声明几个常量对象" - smartcaveman
@smartcaveman 是的,但我们似乎已经决定枚举并不是他想要的。我认为他可能想要的是一些数据的文件,这些数据被读入一个数组中。然而,你说得对,他在问题中确实提到了常量,很好的发现。 - CrazyCasta

1

另一种方法是标志枚举

[Flags]
enum Car
{
  None = 0, 
  ModelFord = 1,
  ModelBmw = 2,
  ModelToyota = 4,
  ColorRed = 8,
  ColorBlack = 16,
  carA = ModelFord | ColorRed,
  carB = ModelBmw | ColorBlack,
  carC = ModelToyota  | ColorBlack
}

请注意,这只是一个示例 - 您应该避免在单个枚举中混合属性类型(例如汽车型号和颜色)。

(1) 如果某个行为仍然可能发生,那么说“你应该避免这样做”有点危险。我想知道是否有一种使用位运算逻辑来安全地组合这些值的方法。 (2) None 对于一个 Flags 枚举 来说是不好的语义。考虑使用:Car.carA.HasFlag(Car.None) - smartcaveman
@smartcaveman(1)可以使用“ModelMask=0x0f”使模型1-15连续,“ColorMask=0xf0”并将颜色1-15向右移4位,以便将值打包到一个枚举中。但对于常规代码,在同一枚举中混合属性看起来会很混乱。(2)有更好的解决方案吗?0始终存在,因此MSDN建议基本上是将其保留并明确表示,而不是可能错误地将其中一个有效值设置为0。 - Alexei Levenkov
我更喜欢使用“默认值”而不是“无”,因为“无”可能被理解为没有标志。虽然我以前没有这样做过,但现在想想,也许可以用“_”? - smartcaveman
@smartcaveman,我同意在这个示例中使用“None”没有意义,最好使用“Default”、“Unknown”等更好的选项 - 但我不会在真实代码中首先编写它。我认为“None”对于真正的Flag类型枚举(我的示例不是其中之一)可以很好地工作,并且据我记得,它也受到FxCop的强制执行。 - Alexei Levenkov

1
一个小技巧,让Carenum一样工作,定义如下:
internal enum Maker
{
    Ford, Bmw, Toyota,
}

internal enum Color
{
    Red, Black, White
}

然后构建结构体Car:
public struct Car
{
    private readonly Maker _maker;
    private readonly Color _color;

    public static Car CarA = new Car(Maker.Ford, Color.Red);
    public static Car CarB = new Car(Maker.Bmw, Color.Black);
    public static Car CarC = new Car(Maker.Toyota, Color.White);


    private Car(Maker maker, Color color)
    {
        _maker = maker;
        _color = color;
    }

    public static bool operator ==(Car car1, Car car2)
    {
        return car1._maker == car2._maker && car1._color == car2._color;
    }

    public static bool operator !=(Car car1, Car car2)
    {
        return !(car1 == car2);
    }
}

所以,您可以使用:

 Car a = Car.CarA;
 bool flag = a == Car.CarB;

当实际上应该返回 true 时,car1.Equals(car2) 仍会返回 false。您需要重写 EqualsGetHashCode。此外,在访问属性实例之前,应检查是否为 null,否则可能会引发 NullReferenceException - smartcaveman
@smartcaveman:理论上应该是这样,但是模拟“伪枚举”非常容易。 - cuongle
我以为Car被声明为一个类,但它是一个结构体。 - smartcaveman

1

您可以使用静态类来存储扩展方法,以公开您的额外数据。例如:

enum Car
{
    CarA, CarB, CarC
}

public static class Cars 
{
    public static string[] GetDetails(this Car car) 
    {
        switch (car) 
        {
            case CarA: return new[] { "ford", "red" };
            case CarB: return new[] { "bmw", "black" };
            case CarC: return new[] { "toyota", "white" };
        }
    }
}

话虽如此,但我认为返回字符串数组并不太合理。我会声明两个扩展方法,一个用于制造商,一个用于颜色:

public static class Cars 
{
    public static string GetMake(this Car car) 
    {
        switch (car) 
        {
            case CarA: return "ford";
            case CarB: return "bmw";
            case CarC: return "toyota";
        }
    }

    public static string GetColor(this Car car) 
    {
        switch (car) 
        {
            case CarA: return "red";
            case CarB: return "black";
            case CarC: return "white";
        }
    }
}

然后您可以这样使用它:

Car car = Car.CarA;
string make = car.GetMake();
string color = car.GetColor();

1

关于属性的使用怎么样?

enum Cars{
  [Make("A Make"), Color("A Color")]
  CarA,

  [Make("B Make"), Color("B Color")]
  CarB
}

然后像这样定义属性。

public class MakeAttribute : Attribute
    {
        public readonly Make make;
        public MakeAttribute (Make make)
        {
          this.make = make;
        }
    }

为汽车类型添加一个扩展,以获取制造商属性

public static string GetMake(this Car car)
        {
            var makeAttr = (MakeAttribute[])car.GetType().GetField(car.ToString()).GetCustomAttributes(typeof(MakeAttribute), false))[0];
            return makeAttr.make;
        }

而要调用这个getter方法,

Cars.CarA.GetMake()

这不是一个坏答案,但没有展示如何访问属性就不完整了。 - Eric J.

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