【问题标题】:Enum "Inheritance"枚举“继承”
【发布时间】:2010-10-19 22:49:56
【问题描述】:

我在低级命名空间中有一个枚举。我想在“继承”低级枚举的中级命名空间中提供一个类或枚举。

namespace low
{
   public enum base
   {
      x, y, z
   }
}

namespace mid
{
   public enum consume : low.base
   {
   }
}

我希望这是可能的,或者可能是某种可以代替枚举消耗的类,它将为枚举提供一个抽象层,但仍然让该类的实例访问枚举。

想法?

编辑: 我不只是在类中将其切换为 consts 的原因之一是我必须使用的服务需要低级枚举。我得到了 WSDL 和 XSD,它们将结构定义为枚举。服务无法更改。

【问题讨论】:

标签: c# .net enums


【解决方案1】:

这是不可能的。枚举不能从其他枚举继承。事实上,所有枚举实际上都必须继承自 System.Enum。 C# 允许语法改变枚举值的底层表示,看起来像继承,但实际上它们仍然继承自 System.enum。

有关详细信息,请参阅CLI spec 的第 8.5.2 节。规范中的相关信息

  • 所有枚举必须派生自System.Enum
  • 由于上述原因,所有枚举都是值类型,因此是密封的

【讨论】:

  • 所有值类型都派生自 System.ValueType
  • 不得不提@Seven 的回答是一个合法的解决方法:stackoverflow.com/a/4042826/538387
  • 但@Steven 的回答不能在switch 情况下使用。
  • @zionpi 是的,但请注意(我相信)标准开关被编译为与完整的 if、else if、else 块相同的 IL 代码:我认为无论如何它都有更好的语法。您确实无法让 resharper / VS 自动完成所有案例陈述,但我认为这不是世界末日。这是个人喜好,但我不喜欢 switch 语句。
【解决方案2】:

你可以通过类实现你想要的:

public class Base
{
    public const int A = 1;
    public const int B = 2;
    public const int C = 3;
}
public class Consume : Base
{
    public const int D = 4;
    public const int E = 5;
}

现在您可以像使用枚举时一样使用这些类:

int i = Consume.B;

更新(在您更新问题后):

如果您为现有枚举中定义的常量分配相同的 int 值,那么您可以在枚举和常量之间进行转换,例如:

public enum SomeEnum // this is the existing enum (from WSDL)
{
    A = 1,
    B = 2,
    ...
}
public class Base
{
    public const int A = (int)SomeEnum.A;
    //...
}
public class Consume : Base
{
    public const int D = 4;
    public const int E = 5;
}

// where you have to use the enum, use a cast:
SomeEnum e = (SomeEnum)Consume.B;

【讨论】:

  • 那么你如何枚举这个类中的字段呢?对我来说,这是枚举的重要行为:Enum.GetValues(typeof(MyEnum)
  • 可以使用反射:void Test() { foreach (System.Reflection.PropertyInfo pi in typeof(Consume).GetProperties()) { Console.WriteLine(pi.Name); } }
  • 您需要确保使用反射收集属性会忽略继承的 Object 属性。
  • 反射对于该任务来说非常难看。
  • 不需要使用反射,codeproject.com/Articles/20805/Enhancing-C-Enums 的实现是一个很好的方法,因为正在创建对象,它们被添加到列表中,并且该列表可用于返回对象类型列表。当您将其与继承混合时,您必须确保为继承类使用正确的列表。
【解决方案3】:

简短的回答是否定的。如果你愿意,你可以玩一下:

你总是可以这样做:

private enum Base
{
    A,
    B,
    C
}

private enum Consume
{
    A = Base.A,
    B = Base.B,
    C = Base.C,
    D,
    E
}

但是,它并没有那么好用,因为 Base.A != Consume.A

不过,您总是可以这样做:

public static class Extensions
{
    public static T As<T>(this Consume c) where T : struct
    {
        return (T)System.Enum.Parse(typeof(T), c.ToString(), false);
    }
}

为了在 Base 和 Consume 之间进行交叉...

您也可以将枚举的值转换为整数,并将它们作为整数而不是枚举进行比较,但这也很糟糕。

扩展方法返回应该类型转换为 T。

【讨论】:

  • 我喜欢这个,伙计。使用这个概念将一些枚举从我的 ORM 冒泡到我的公共接口(对于那些没有 ORM 引用的)。
  • 可以转换枚举以与工作进行比较:Base.A == (Base)Consume.A
  • 使用(十进制)Base.A ==(十进制)Consume.A。原因:这就是位标志/掩码组合的工作方式(例如 Enum.IsDefined msdn.microsoft.com/en-us/library/… 中的示例)。因此,可以将枚举设置为枚举中未定义的整数。消费测试 = 123456;
  • @TamusJRoyce 十进制? int 会更有意义。枚举什么时候会有小数部分!?!?!
  • 至少,这样可以确保对应的枚举常量具有相同的整数值。毕竟,枚举只是一组整数常量。 C# 不强制分配的值是有效的枚举常量,即枚举在严格意义上不是类型安全的。
【解决方案4】:

上述使用具有 int 常量的类的解决方案缺乏类型安全性。 IE。您可以发明实际上未在类中定义的新值。 此外,不可能编写一个将这些类之一作为输入的方法。

你需要写

public void DoSomethingMeaningFull(int consumeValue) ...

但是,Java 的旧时代有一个基于类的解决方案,当时没有可用的枚举。这提供了几乎类似枚举的行为。唯一需要注意的是,这些常量不能在 switch 语句中使用。

public class MyBaseEnum
{
    public static readonly MyBaseEnum A = new MyBaseEnum( 1 );
    public static readonly MyBaseEnum B = new MyBaseEnum( 2 );
    public static readonly MyBaseEnum C = new MyBaseEnum( 3 );

    public int InternalValue { get; protected set; }

    protected MyBaseEnum( int internalValue )
    {
        this.InternalValue = internalValue;
    }
}

public class MyEnum : MyBaseEnum
{
    public static readonly MyEnum D = new MyEnum( 4 );
    public static readonly MyEnum E = new MyEnum( 5 );

    protected MyEnum( int internalValue ) : base( internalValue )
    {
        // Nothing
    }
}

[TestMethod]
public void EnumTest()
{
    this.DoSomethingMeaningful( MyEnum.A );
}

private void DoSomethingMeaningful( MyBaseEnum enumValue )
{
    // ...
    if( enumValue == MyEnum.A ) { /* ... */ }
    else if (enumValue == MyEnum.B) { /* ... */ }
    // ...
}

【讨论】:

  • 我认为这是正确的答案。你不能继承 Enum,但这可以让你管理它!
  • 干净整洁。 +1。只是一个提示,你真的根本不需要 int 值。
  • 我从来没有想过枚举,好像你可以说 FileOpenMode.Read > FileOpenMode.Write。如果是这种情况,那么对于该枚举,您需要一个 int 甚至更好的 IEqualityComparer。问候。
  • @binki 使用随机的object's 会使程序集的每个实例化的值不同,因此它们不能被序列化。
  • 但这不允许您“切换”MyEnum,是吗?我的意思是这主要是我使用枚举的原因...
【解决方案5】:

忽略 base 是保留字这一事实,您不能继承枚举。

你能做的最好的事情就是这样:

public enum Baseenum
{
   x, y, z
}

public enum Consume
{
   x = Baseenum.x,
   y = Baseenum.y,
   z = Baseenum.z
}

public void Test()
{
   Baseenum a = Baseenum.x;
   Consume newA = (Consume) a;

   if ((Int32) a == (Int32) newA)
   {
   MessageBox.Show(newA.ToString());
   }
}

由于它们都是相同的基本类型(即:int),您可以将值从一种类型的实例分配给另一种类型的实例。不理想,但它有效。

【讨论】:

  • base 是保留的,但 Base 不是
  • 他指的是 OP 使用 base 作为枚举名称,我确定这只是一个示例名称
  • 与 Genisio 的回答相同。
【解决方案6】:

我知道这个答案有点晚了,但这就是我最终要做的:

public class BaseAnimal : IEquatable<BaseAnimal>
{
    public string Name { private set; get; }
    public int Value { private set; get; }

    public BaseAnimal(int value, String name)
    {
        this.Name = name;
        this.Value = value;
    }

    public override String ToString()
    {
        return Name;
    }

    public bool Equals(BaseAnimal other)
    {
        return other.Name == this.Name && other.Value == this.Value;
    }
}

public class AnimalType : BaseAnimal
{
    public static readonly BaseAnimal Invertebrate = new BaseAnimal(1, "Invertebrate");

    public static readonly BaseAnimal Amphibians = new BaseAnimal(2, "Amphibians");

    // etc        
}

public class DogType : AnimalType
{
    public static readonly BaseAnimal Golden_Retriever = new BaseAnimal(3, "Golden_Retriever");

    public static readonly BaseAnimal Great_Dane = new BaseAnimal(4, "Great_Dane");

    // etc        
}

然后我可以执行以下操作:

public void SomeMethod()
{
    var a = AnimalType.Amphibians;
    var b = AnimalType.Amphibians;

    if (a == b)
    {
        // should be equal
    }

    // call method as
    Foo(a);

    // using ifs
    if (a == AnimalType.Amphibians)
    {
    }
    else if (a == AnimalType.Invertebrate)
    {
    }
    else if (a == DogType.Golden_Retriever)
    {
    }
    // etc          
}

public void Foo(BaseAnimal typeOfAnimal)
{
}

【讨论】:

  • 幻数可以替换为stackoverflow.com/questions/757684/… 中的对象。但是对于这种特殊情况,您可以通过使用现有生物命名法的特征来保证唯一性,从而获得两全其美。
  • DogTypeAnimalType 是否有理由从 BaseAnimal 继承?根据我所看到的,这些可以制成静态类
【解决方案7】:

这就是我所做的。我所做的不同是在“消费”enum 上使用相同的名称和new 关键字。因为enum的名字是一样的,随便用就行了。另外,您可以获得智能感知。您只需在设置时手动注意从基础复制值并使其保持同步。您可以与代码 cmets 一起提供帮助。这是在数据库中存储enum 值时我总是存储字符串而不是值的另一个原因。因为如果您使用自动分配的递增整数值,这些值可能会随着时间而改变。

// Base Class for balls 
public class Ball
{
    // keep synced with subclasses!
    public enum Sizes
    {
        Small,
        Medium,
        Large
    }
}

public class VolleyBall : Ball
{
    // keep synced with base class!
    public new enum Sizes
    {
        Small  = Ball.Sizes.Small,
        Medium = Ball.Sizes.Medium,
        Large  = Ball.Sizes.Large,
        SmallMedium,
        MediumLarge,
        Ginormous
    }
}

【讨论】:

  • 考虑为派生类中的新值设置不同的范围(例如SmallMedium = 100,),以便在基类中添加新值时可以保持与旧版本软件的兼容性。例如,在基本枚举中添加Huge 大小会将4 分配给它的值,但4 已经被派生类中的SmallMedium 采用。
  • @Roberto,为了解决这个问题,我从不保留枚举值,而只保留名称。保持它们同步是这里的要求。所以在基类中添加Huge 需要在SmallMedium 之前的子类中添加Huge
【解决方案8】:

替代解决方案

在我的公司,我们避免“跳过项目”来处理不常见的较低级别的项目。比如我们的表现层/API层只能引用我们的领域层,领域层只能引用数据层。

但是,当存在表示层和域层都需要引用的枚举时,这是一个问题。

这是我们(到目前为止)实施的解决方案。这是一个很好的解决方案,对我们来说效果很好。其他答案都在这方面。

基本前提是枚举不能被继承——但类可以。所以...

// In the lower level project (or DLL)...
public abstract class BaseEnums
{
    public enum ImportanceType
    {
        None = 0,
        Success = 1,
        Warning = 2,
        Information = 3,
        Exclamation = 4
    }

    [Flags]
    public enum StatusType : Int32
    {
        None = 0,
        Pending = 1,
        Approved = 2,
        Canceled = 4,
        Accepted = (8 | Approved),
        Rejected = 16,
        Shipped = (32 | Accepted),
        Reconciled = (64 | Shipped)
    }

    public enum Conveyance
    {
        None = 0,
        Feet = 1,
        Automobile = 2,
        Bicycle = 3,
        Motorcycle = 4,
        TukTuk = 5,
        Horse = 6,
        Yak = 7,
        Segue = 8
    }

然后,“继承”另一个更高级别项目中的枚举...

// Class in another project
public sealed class SubEnums: BaseEnums
{
   private SubEnums()
   {}
}

这具有三个真正的优势......

  1. 两个项目中的枚举定义自动相同 - 通过 定义。
  2. 对枚举定义的任何更改都会自动 在第二个中呼应而无需对 二等。
  3. 枚举基于相同的代码 - 因此可以轻松比较这些值(有一些注意事项)。

要引用第一个项目中的枚举,可以使用类的前缀:BaseEnums.StatusType.Pending或添加一个“使用静态BaseEnums ;" 声明你的使用。

第二个项目中处理继承类时,我无法让“使用静态...”方法工作,所以所有对“继承的枚举”将以类为前缀,例如SubEnums.StatusType.Pending。如果有人想出一种方法来允许在第二个项目中使用 “使用静态” 方法,请告诉我。

我确信可以对此进行调整以使其变得更好 - 但这确实有效,并且我在工作项目中使用了这种方法。

【讨论】:

  • 恕我直言,您所做的是对分层架构的误解。传输对象、接口等类似合同的结构可以而且应该在各层之间共享。限制您仅在处理管道期间束缚自己的双手,有时会丢失上下文信息,这些信息应稍后在管道中从上下文和默认值中恢复。我已经多次看到这个错误。 GRPC 等现代框架也建议在您的架构中垂直共享这些结构。
  • 此解决方案回答了所提出的问题,并且在没有枚举的公共共享源的情况下有效。我不会将此描述为“错误”。
  • 好吧,这不是错误,这是一种反模式。您将共享结构定义视为不是的层。然后你需要使用符号、前缀和后缀来区分它们。我总是尝试在我的应用程序中以不同的方式命名事物。如果我找不到合适的名称,我会分析并重新考虑是否需要它。我避免使用符号和约定。这只是需要学习和记住的额外知识。我尽可能多地分享代码。这是消除冗余的方法。毕竟我不是我的敌人
  • 从来没有说过这是最好的方法。我所说的是它回答了被问到的问题。有时这就是软件在特定环境下所能做的所有事情。在某些情况下,无法从公共来源共享定义 - 这个答案解决了这个问题。
【解决方案9】:

我还想重载 Enums,并创建了 the answer of 'Seven' on this pagethe answer of 'Merlyn Morgan-Graham' on a duplicate post of this 的组合,以及一些改进。
我的解决方案与其他解决方案相比的主要优势:

  • 基础 int 值的自动递增
  • 自动命名

这是一个开箱即用的解决方案,可以直接插入到您的项目中。它是根据我的需要设计的,所以如果你不喜欢它的某些部分,只需用你自己的代码替换它们。

首先,所有自定义枚举都应继承自基类CEnum。它具有基本功能,类似于.net Enum 类型:

public class CEnum
{
  protected static readonly int msc_iUpdateNames  = int.MinValue;
  protected static int          ms_iAutoValue     = -1;
  protected static List<int>    ms_listiValue     = new List<int>();

  public int Value
  {
    get;
    protected set;
  }

  public string Name
  {
    get;
    protected set;
  }

  protected CEnum ()
  {
    CommonConstructor (-1);
  }

  protected CEnum (int i_iValue)
  {
    CommonConstructor (i_iValue);
  }

  public static string[] GetNames (IList<CEnum> i_listoValue)
  {
    if (i_listoValue == null)
      return null;
    string[] asName = new string[i_listoValue.Count];
    for (int ixCnt = 0; ixCnt < asName.Length; ixCnt++)
      asName[ixCnt] = i_listoValue[ixCnt]?.Name;
    return asName;
  }

  public static CEnum[] GetValues ()
  {
    return new CEnum[0];
  }

  protected virtual void CommonConstructor (int i_iValue)
  {
    if (i_iValue == msc_iUpdateNames)
    {
      UpdateNames (this.GetType ());
      return;
    }
    else if (i_iValue > ms_iAutoValue)
      ms_iAutoValue = i_iValue;
    else
      i_iValue = ++ms_iAutoValue;

    if (ms_listiValue.Contains (i_iValue))
      throw new ArgumentException ("duplicate value " + i_iValue.ToString ());
    Value = i_iValue;
    ms_listiValue.Add (i_iValue);
  }

  private static void UpdateNames (Type i_oType)
  {
    if (i_oType == null)
      return;
    FieldInfo[] aoFieldInfo = i_oType.GetFields (BindingFlags.Public | BindingFlags.Static);

    foreach (FieldInfo oFieldInfo in aoFieldInfo)
    {
      CEnum oEnumResult = oFieldInfo.GetValue (null) as CEnum;
      if (oEnumResult == null)
        continue;
      oEnumResult.Name = oFieldInfo.Name;
    }
  }
}

其次,这里有 2 个派生的 Enum 类。所有派生类都需要一些基本方法才能按预期工作。它总是相同的样板代码;我还没有找到将它外包给基类的方法。第一级继承的代码与所有后续级别略有不同。

public class CEnumResult : CEnum
{
  private   static List<CEnumResult>  ms_listoValue = new List<CEnumResult>();

  public    static readonly CEnumResult Nothing         = new CEnumResult (  0);
  public    static readonly CEnumResult SUCCESS         = new CEnumResult (  1);
  public    static readonly CEnumResult UserAbort       = new CEnumResult ( 11);
  public    static readonly CEnumResult InProgress      = new CEnumResult (101);
  public    static readonly CEnumResult Pausing         = new CEnumResult (201);
  private   static readonly CEnumResult Dummy           = new CEnumResult (msc_iUpdateNames);

  protected CEnumResult () : base ()
  {
  }

  protected CEnumResult (int i_iValue) : base (i_iValue)
  {
  }

  protected override void CommonConstructor (int i_iValue)
  {
    base.CommonConstructor (i_iValue);

    if (i_iValue == msc_iUpdateNames)
      return;
    if (this.GetType () == System.Reflection.MethodBase.GetCurrentMethod ().DeclaringType)
      ms_listoValue.Add (this);
  }

  public static new CEnumResult[] GetValues ()
  {
    List<CEnumResult> listoValue = new List<CEnumResult> ();
    listoValue.AddRange (ms_listoValue);
    return listoValue.ToArray ();
  }
}

public class CEnumResultClassCommon : CEnumResult
{
  private   static List<CEnumResultClassCommon> ms_listoValue = new List<CEnumResultClassCommon>();

  public    static readonly CEnumResult Error_InternalProgramming           = new CEnumResultClassCommon (1000);

  public    static readonly CEnumResult Error_Initialization                = new CEnumResultClassCommon ();
  public    static readonly CEnumResult Error_ObjectNotInitialized          = new CEnumResultClassCommon ();
  public    static readonly CEnumResult Error_DLLMissing                    = new CEnumResultClassCommon ();
  // ... many more
  private   static readonly CEnumResult Dummy                               = new CEnumResultClassCommon (msc_iUpdateNames);

  protected CEnumResultClassCommon () : base ()
  {
  }

  protected CEnumResultClassCommon (int i_iValue) : base (i_iValue)
  {
  }

  protected override void CommonConstructor (int i_iValue)
  {
    base.CommonConstructor (i_iValue);

    if (i_iValue == msc_iUpdateNames)
      return;
    if (this.GetType () == System.Reflection.MethodBase.GetCurrentMethod ().DeclaringType)
      ms_listoValue.Add (this);
  }

  public static new CEnumResult[] GetValues ()
  {
    List<CEnumResult> listoValue = new List<CEnumResult> (CEnumResult.GetValues ());
    listoValue.AddRange (ms_listoValue);
    return listoValue.ToArray ();
  }
}

已使用以下代码成功测试了这些类:

private static void Main (string[] args)
{
  CEnumResult oEnumResult = CEnumResultClassCommon.Error_Initialization;
  string sName = oEnumResult.Name;   // sName = "Error_Initialization"

  CEnum[] aoEnumResult = CEnumResultClassCommon.GetValues ();   // aoEnumResult = {testCEnumResult.Program.CEnumResult[9]}
  string[] asEnumNames = CEnum.GetNames (aoEnumResult);
  int ixValue = Array.IndexOf (aoEnumResult, oEnumResult);    // ixValue = 6
}

【讨论】:

    【解决方案10】:

    我意识到我参加这个聚会有点晚了,但这是我的两分钱。

    我们都清楚框架不支持枚举继承。在这个帖子中提出了一些非常有趣的解决方法,但它们都不像我正在寻找的那样,所以我自己尝试了一下。

    介绍:ObjectEnum

    您可以在此处查看代码和文档:https://github.com/dimi3tron/ObjectEnum

    还有这里的包裹:https://www.nuget.org/packages/ObjectEnum

    或者直接安装:Install-Package ObjectEnum

    简而言之,ObjectEnum&lt;TEnum&gt; 充当任何枚举的包装器。通过覆盖子类中的 GetDefinedValues(),可以指定哪些枚举值对这个特定的类有效。

    添加了许多运算符重载以使 ObjectEnum&lt;TEnum&gt; 实例的行为就像它是底层枚举的实例一样,请记住定义的值限制。这意味着您可以轻松地将实例与 int 或 enum 值进行比较,从而在 switch case 或任何其他条件下使用它。

    我想参考上面提到的 github 存储库以获取示例和更多信息。

    我希望你觉得这很有用。欢迎在 github 上发表评论或打开问题,以获取更多想法或 cmets。

    这里有几个简短的例子来说明你可以用ObjectEnum&lt;TEnum&gt;做什么:

    var sunday = new WorkDay(DayOfWeek.Sunday); //throws exception
    var monday = new WorkDay(DayOfWeek.Monday); //works fine
    var label = $"{monday} is day {(int)monday}." //produces: "Monday is day 1."
    var mondayIsAlwaysMonday = monday == DayOfWeek.Monday; //true, sorry...
    
    var friday = new WorkDay(DayOfWeek.Friday);
    
    switch((DayOfWeek)friday){
        case DayOfWeek.Monday:
            //do something monday related
            break;
            /*...*/
        case DayOfWeek.Friday:
            //do something friday related
            break;
    }
    

    【讨论】:

      【解决方案11】:

      枚举不是实际的类,即使它们看起来很像。在内部,它们的处理方式与它们的基础类型一样(默认为 Int32)。因此,您只能通过将单个值从一个枚举“复制”到另一个枚举并将它们转换为它们的整数来比较它们是否相等。

      【讨论】:

        【解决方案12】:

        枚举不能从其他枚举派生,而只能从 int、uint、short、ushort、long、ulong、byte 和 sbyte 派生。

        就像 Pascal 所说,您可以使用其他枚举的值或常量来初始化枚举值,但仅此而已。

        【讨论】:

        • 由于 c# 语法,这有点用词不当,但枚举实际上不能从 int、uint 等继承……在幕后,它们仍然继承自 System.Enum。只是代表枚举的成员被输入到int、uint等...
        • @JaredPar。当一个枚举是从一个 uint 派生的,这意味着它的值都是 uint 等。默认情况下,枚举继承 int。 (看看 C# 规范, enum SomeEnum : uint { ... } 确实有效。)
        • 其实没有。它继承 System.enum。正如之前和更频繁地在这里发布的那样,您认为继承只是 csharp 中的一种语言模糊性。
        【解决方案13】:

        另一种可能的解决方案:

        public enum @base
        {
            x,
            y,
            z
        }
        
        public enum consume
        {
            x = @base.x,
            y = @base.y,
            z = @base.z,
        
            a,b,c
        }
        
        // TODO: Add a unit-test to check that if @base and consume are aligned
        

        HTH

        【讨论】:

          【解决方案14】:

          这是不可能的(正如@JaredPar 已经提到的)。试图用逻辑来解决这个问题是一种不好的做法。如果你有一个base class 和一个enum,你应该在那里列出所有可能的enum-values,并且类的实现应该使用它知道的值。

          例如假设你有一个基类BaseCatalog,它有一个enum ProductFormatsDigitalPhysical)。然后你可以有一个MusicCatalogBookCatalog 可以包含DigitalPhysical 产品,但如果类是ClothingCatalog,它应该只包含Physical 产品。

          【讨论】:

            【解决方案15】:

            您可以在枚举中执行继承,但仅限于以下类型。 int, uint, byte, sbyte, short, ushort, long, ulong

            例如

            public enum Car:int{
            Toyota,
            Benz,
            }
            

            【讨论】:

            • 我认为 OP 要求从另一个枚举继承一个枚举,而不仅仅是从基本数字类型(所有枚举在 C# 中隐式或显式地这样做。)
            猜你喜欢
            • 2016-01-16
            • 2012-04-04
            • 2011-03-07
            • 1970-01-01
            相关资源
            最近更新 更多