【问题标题】:Can you add to an enum type in run-time您可以在运行时添加到枚举类型吗
【发布时间】:2010-05-06 08:53:50
【问题描述】:

如果我有一个枚举类型:

public enum Sport
{
    Tennis = 0;
    Football = 1;
    Squash = 2;
    Volleyball = 3;
}

我可以在运行时以某种方式添加吗:

PingPong = 4

【问题讨论】:

  • 您可以使用列表或字典来模仿这种行为吗?
  • 您希望在什么场景中使用它?
  • 我继承了代码,其中存在需要根据用户输入扩展的枚举类型,从而使枚举成为不合适的类型,但我需要短期内的解决方法。

标签: c# enums runtime


【解决方案1】:

枚举有一个后备存储,如果您不指定它,则默认为 int。可以直接分配定义值之外的值:

Sport pingPong = (Sport)4;

然后你可以检查它:

if (value == (Sport)4) {}

这就是为什么你有静态函数Enum.IsDefined() 来检查实际值是否在预期值之内。请注意,该函数不适用于复合标志值。

bool isValueDefined = Enum.IsDefined(typeof(Sport), value);

编辑:在 Hans Passant 的评论之后:您不必使用文字值 4。您可以使用任何返回 int 的东西。例如:

Dictionary<int, string> AdditionalSports = new Dictionary<int, string>();
AdditionalSports.Add(4, "PingPong");

// Usages: if
if (AdditionalSports.ContainsKey(value))
{
    // Maybe do something with AdditionalSports[value], i.e. "PingPong"
}

// In a switch:
switch (value)
{
case default:
    // Since it won't be found in the enum-defined values
    if (AdditionalSports.ContainsKey(value))
    {
        // Maybe do something with AdditionalSports[value], i.e. "PingPong"
    }
}

【讨论】:

  • 我看不到它在运行时的情况,因为在源代码中声明了“PingPong”。
  • 不是,字面量4也可以是变量。这是你能得到的最好的。
  • Console.WriteLine("我的运动是:" + pingpong); 的输出是什么? ?
  • 运行时在 pingpong 上隐式调用 ToString()。如果 pingpong 具有定义的枚举值(例如 Sport.Tennis),则结果是枚举值作为文本(即“我的运动是:网球”)。如果 pingpong 未定义(例如 4),则它是 int 值本身(即“我的运动是:4”)。您可以使用 AdditionalSports 字典,即 Console.WriteLine("我的运动是:" + AdditionalSports[pingpong]);获得“我的运动是:乒乓球”。
  • 现在可以使用C#代码生成功能了
【解决方案2】:

这里有更多面向对象的方式来实现您想要实现的目标。该解决方案受到早期 Java 枚举方法的启发:

struct Sport {
    readonly int value;
    public Sport(int value) {
        this.value = value;
    }
    public static implicit operator int(Sport sport) {
        return sport.value;
    }
    public static implicit operator Sport(int sport) {
        return new Sport(sport);
    }

    public const int Tennis =       0;
    public const int Football =     1;
    public const int Squash =       2;
    public const int Volleyball =   3;
}

//Usage:
Sport sport = Sport.Volleyball;
switch(sport) {
    case Sport.Squash:
        Console.WriteLine("I bounce really high");
        break;
}
Sport rugby = 5;
if (sport == rugby)
    Console.WriteLine("I am really big and eat a lot");

了解此解决方案的不同功能。

  1. 它是一个不可变结构,包含一个整数值。 readonly 关键字强制该值不可变。

  2. 创建这些结构之一的唯一方法是调用将值作为参数的构造函数。

  3. implicit operator int 在那里,因此可以在 switch 块中使用该结构 - 即使该结构可转换为 int

  4. implicit operator Sport 在那里,您可以将整数值分配给结构,即Sport rugby = 5

  5. const 值是编译时已知的运动。它们也可以用作case 标签。

我实际上会做什么

public static class Sports {
    public static readonly Sport Football = new Sport("Football");
    public static readonly Sport Tennis = new Sport("Tennis");
}

public class Sport {
    public Sport(string name) {
        Name = name;
    }
    public string Name { get; private set; }

    // override object.Equals
    public override bool Equals(object obj) {
        var other = obj as Sport;
        if(other == null) {
            return false;
        }

        return other == this;
    }

    // override object.GetHashCode
    public override int GetHashCode() {
        return Name.GetHashCode();
    }

    public static bool operator == (Sport sport1, Sport sport2) {
        if(Object.ReferenceEquals(sport1, null) && Object.ReferenceEquals(sport2 , null))
            return true;

        if(Object.ReferenceEquals(sport1, null) || Object.ReferenceEquals(sport2, null))
            return false;

        return sport1.Name == sport2.Name;
    }
    public static bool operator !=(Sport sport1, Sport sport2) {
        return !(sport1 == sport2);
    }
}

这将创建一个具有名称的值类Sport。根据您的应用程序,您可以扩展此类以提供其他属性和方法。将此作为类为您提供更大的灵活性,因为您可以将其子类化。

Sports 类提供了在编译时已知的静态运动集合。这类似于某些 .NET 框架处理命名颜色(即 WPF)的方式。用法如下:

List<Sport> sports = new List<Sport>();

sports.Add(Sports.Football);
sports.Add(Sports.Tennis);
//What if the name contains spaces?
sports.Add(new Sport("Water Polo"));

var otherSport = new Sport("Other sport");

if(sports.Contains(otherSport)) {
    //Do something
}

foreach(var sport in sports) {
    if(sport == otherSport) {
        //Do Something
    } else if(sport == Sports.Football) {
        //do something else
    }
}

完成此操作后,您会发现实际上几乎不需要枚举,因为运动类型的任何条件操作都可以在 Sport 类中处理。

编辑意识到我的相等运算符会抛出一个StackOverflowException 我总是忘记写Object.ReferenceEquals(obj,null) 而不是obj==null,这将无限递归。

【讨论】:

  • 你觉得 Daniel Rose 的回答怎么样?
  • 我都不会选择。在运行时用东西补充枚举有点臭。对于在编译时和运行时已知的运动,我会选择一种一致的方法。稍后会给出示例。
  • 我会改用public const Sport Tennis = new Sport(0)
  • @tster 这也是我的第一个想法。但是,您不能将用户定义的结构或类的实例定义为 const。它们必须是简单的内置类型或字符串。您将它们定义为只读,但随后您将失去将它们用作案例标签的能力。
【解决方案3】:

不,您不能在运行时修改类型。您可以发出新类型,但无法修改现有类型。

【讨论】:

  • 发出的新类型可以替换现有的吗?
  • 不,它不能,因为这意味着您可以在运行时修改现有类型,而您不能。
  • 如果其他答案正在添加,为什么你说你不能向现有枚举添加值?
【解决方案4】:

是的,您可以使用EnumBuilder 类在运行时创建和/或更改枚举,请参阅 MSDN 中的示例

ModuleBuilder mb = ab.DefineDynamicModule(aName.Name, aName.Name + ".dll");
EnumBuilder eb = mb.DefineEnum("Elevation", TypeAttributes.Public, typeof(int));
eb.DefineLiteral("Low", 0);
eb.DefineLiteral("High", 1);
Type finished = eb.CreateType();

在我看来,拥有一个枚举值比字典或基于列表的解决方案更可取,因为它使用更少的内存并且没有线程副作用。

Here are some samples 了解如何在重新启动应用程序时加载生成的枚举。我建议你选择一个适合你的,你可以使用 System.AddIn 命名空间提供的方法,我猜……或者使用你的 IoC。

当我需要为机器学习生成具有 Enum 值的数据时,我会使用它,并且比查找表(在内存或数据库中)更可取,因为我只是没有可用于这些大型数据集的 IO。

【讨论】:

    【解决方案5】:

    如果枚举可以在程序启动时定义,则将枚举放在单独的程序集中并使用引导程序重新编译枚举,覆盖旧版本,然后启动实际应用程序。这不是最干净的方法,但很有效。

    【讨论】:

      【解决方案6】:

      我会创建一个完整的枚举,其中包含您需要的所有值

      Public enum Sport
      {
          Tennis = 0;
          Football = 1;
          Squash = 2;
          Volleyball = 3;
          PingPong = 4;
          Rugby = 5;  // for example
      }
      

      然后保留一个无效运动条目列表,因此该列表最初将包含 PingPongRugby。每次访问 Enum 时,还要检查您的无效运动列表。

      然后您可以简单地调整您的无效运动列表以适应任何阶段

      【讨论】:

        【解决方案7】:

        我正在尝试上面的代码,但没有工作,给了我一堆错误,这就是我解决它的方法:

        using System;
        for (int i = 0; i < Enum.GetValues(typeof(ButtonName)).Length; i++){
            Enum.GetValues(typeof(ButtonName)).SetValue((ButtonName)Enum.GetValues(typeof(ButtonName)).GetValue(i), i);
        }
        public enum ButtonName{
            Person,
            money,
            salary
        }
        

        这是在运行时评估它们的方法

        this 是我发现如何处理所有这些问题的想法,即使它们没有被重视。

        投票最多的答案给了我一些灵感,即使我从他那里得到一个错误,我也应该只设置一个。

        希望这篇文章对某人有所帮助

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2010-10-03
          • 2010-11-27
          • 2011-03-08
          • 2015-12-07
          • 2021-05-17
          • 2023-03-25
          • 2019-01-05
          相关资源
          最近更新 更多