web-dev-qa-db-ja.com

列挙型 "継承"

低レベルの名前空間に列挙型があります。低レベルの列挙型を「継承する」中間レベルの名前空間でクラスまたは列挙型を提供したいです。

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

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

私はこれが可能であることを望んでいます、あるいはおそらくenumの抽象化の層を提供するenumの消費の代わりをすることができるけれどもそのクラスのインスタンスにenumにアクセスさせることができるある種のクラス。

考えですか?

編集:私がこれをクラスのconstに切り替えていない理由の1つは、低レベルのenumが私が消費しなければならないサービスによって必要とされるということです。構造体をenumとして定義するWSDLとXSDが与えられました。サービスは変更できません。

357
CodeMonkey1313

これは不可能です。列挙型は他の列挙型から継承できません。実際、すべてのenumは実際にはSystem.Enumから継承する必要があります。 C#を使用すると、構文で継承のように見えるenum値の基本表現を変更できますが、実際には依然としてSystem.enumから継承しています。

詳しくは CLI仕様 の8.5.2節をご覧ください。仕様からの関連情報

  • すべてのenumはSystem.Enumから派生しなければなりません
  • 上記のため、すべてのenumは値型であり、したがって封印されています
433
JaredPar

あなたはクラスであなたが望むものを達成することができます:

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;

更新(質問の更新後):

既存のenumで定義されているのと同じint値を定数に割り当てると、enumと定数の間でキャストすることができます。例えば:

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;
153
M4N

短い答えはノーです。あなたが望むなら、あなたは少し遊ぶことができます:

あなたはいつでもこのようなことをすることができます:

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

ベースと消費の間を渡るためには...

列挙型の値を整数としてキャストし、それらを列挙型ではなく整数型として比較することもできますが、そのようなことも問題ありません。

拡張メソッドの戻り値は、タイプTにキャストする必要があります。

107
Brian Genisio

Int定数を持つクラスを使用した上記の解決策は、型安全性に欠けています。すなわち実際にはクラスに定義されていない新しい値を発明することができます。さらに、例えばこれらのクラスの1つを入力として取るメソッドを書くことはできません。

あなたが書く必要があるでしょう

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) { /* ... */ }
    // ...
}
91
Seven

Baseが予約語であるという事実を無視すると、enumの継承はできません。

あなたができる最善のことはそのようなものです:

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)なので、ある型のインスタンスから他の型にキャストして値を代入できます。理想的ではありませんが、うまくいきます。

13
Pascal

私はこの答えが遅くなっていることを知っていますが、これは私がやってしまったものです:

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)
{
}
6
Tono Nam

これが私がしたことです。私が違うやり方をしたのは、同じ名前と "浪費" newenumキーワードを使うことです。 enumの名前は同じなので、あなたはそれをただ気にせずに使うことができ、それは正しいでしょう。さらに、あなたは知性を得ます。値をベースからコピーして同期させるように設定するときは、手動で注意する必要があります。あなたはコードのコメントと共にそれを手助けすることができます。これが、データベースにenum値を格納するときに、値ではなく常に文字列を格納するもう1つの理由です。自動的に割り当てられる増加する整数値を使用している場合、それらは時間の経過とともに変化する可能性があるためです。

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

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

列挙型は他の列挙型から派生することはできませんが、int、uint、short、ushort、long、ulong、byte、およびsbyteからのみ派生することができます。

Pascalが言ったように、enum値を初期化するために他のenumの値や定数を使うことができますが、それはそれについてです。

1
Jeroen Landheer

別の可能な解決策:

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

1
ShloEmi

これは不可能です(@JaredParは既に述べたように)。これを回避するためにロジックを使用しようとするのは悪い習慣です。 enumを持つbase classがある場合は、そこにすべての可能なenum-valuesをリ​​ストする必要があり、classの実装はそれが知っている値で動作するはずです。

例えば。基本クラスBaseCatalogがあり、それにenum ProductFormatsDigitalPhysical)があるとします。 MusicCatalogBookCatalogの両方の製品を含むことができるDigitalまたはPhysicalを持つことができますが、クラスがClothingCatalogの場合は、Physicalの製品のみを含める必要があります。

1
Jaider

列挙型は、たとえそのように見えても、実際のクラスではありません。内部的には、それらは基本的な型と同じように扱われます(デフォルトではInt32)。したがって、これを行うことができるのは、単一の値をある列挙型から別の列挙型に「コピー」し、それらを等しいかどうか比較するためにそれらの整数にキャストすることだけです。

1
Lucero

私はまたEnumsをオーバーロードしたかったし、 このページの '7'の答えの答え 'Merlyn Morgan-Graham'のミックスを作成しました。これの重複記事 と、いくつかの改良点。
私のソリューションの他のものに対する主な利点:

  • 基になる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
}
0
Tobias Knauss