web-dev-qa-db-ja.com

C ++またはC#のネストされた列挙型?

最近のいくつかのプロジェクトで、列挙型を使用して状態やタイプなどを表現する問題が繰り返し発生しており、いくつかの条件をチェックする必要があります。これらの条件のいくつかは複数の列挙型にまたがっているため、switchブロックとifブロックにロジックがロードされてしまいます。これは、列挙型を使用してWebリクエストまたは計算から取得しているintをチェックする場合など、列挙型との間でキャストを行う必要がある場合に特に問題になります。

ネストされた列挙型として使用できるC++またはC#の何かはありますか?このようなもの:

_enum Animal
{
    Mammal
    {
        Cat,
        Dog,
        Bear
    },
    Bird
    {
        Pigeon,
        Hawk,
        Ostrich
    },
    Fish
    {
        Goldfish,
        Trout,
        Shark
    },
    Platypus    //srsly what is that thing
};
_

明らかに、そのように宣言されている場合とされていない場合がありますが、アイデアは理解できます。ポイントは、コードで_Animal thisAnimal = Animal.Mammal.Cat_のように使用して、後でif (thisAnimal.IsMember(Animal.Mammal))またはそのようなものをチェックできることです。

私はJavaのEnumSetを見て、かなり便利だと思っていましたが、私が求めている機能と完全に一致しているとは思いません。この例では、1つのレベルですべての動物を含む列挙型を宣言し、それらすべてを関連するセットに追加する必要があります。つまり、元の列挙型を使用する場合、MammalInvertebrateなどの高レベルのものは、_African Swallow_などの非常に具体的なものと同じ「レベル」に表示され、それらは(ある程度)交換可能であったことは事実ではありません。理論的には、上記の入れ子構造では、必要な「特異性」のレベルを指定できるため、次のようになります。

_enum Animal::Kingdom.Order.Family.Genus.Species
{ /*stuff*/ }

Organism::Kingdom.Phylum.Class.Order.Family thisThing;

thisThing = Animalia.Cordata;                               //compiler error
thisThing = Animalia.Chordata.Aves.Passeri.Hirundinidae;    //compiles OK
_

このような構造はどこにも存在しますか?そうでない場合、どのようにC++やC#用にビルドして、可能な限り汎用的で再利用可能なままにすることができますか?

7
anaximander

私はこれが過度に設計されているように見えることを他の人に同意します。通常、クラスの単純な列挙型または複雑な階層が必要ですが、2つを組み合わせることはお勧めできません。

ただし、これを本当に(C#で)実行したい場合は、正確に何を実行したいかを要約すると便利だと思います。

  • 階層KingdomPhylumなどのタイプを分離します。これらはしない継承階層を形成します(そうでない場合、PhylumKingdomに割り当てることができます)。ただし、共通の基本クラスから継承できます。
  • _Animalia.Chordata.Aves_のような各式は変数に割り当て可能である必要があります。つまり、ネストされた静的型ではなく、インスタンスを操作する必要があります。 C#にはグローバル変数がないため、これはルートタイプでは特に問題になります。シングルトンを使用することで解決できます。また、ルートは1つだけにする必要があると思うので、上記のコードは_Organisms.Instance.Animalia.Chordata.Aves_のようになります。
  • _Animalia.Chordata_がコンパイルされるように、各メンバーは異なるタイプである必要がありますが、_Plantae.Chordata_はコンパイルされませんでした。
  • IsMember()メソッドが機能するためには、各メンバーが何らかの方法ですべての子を知っている必要があります。

これらの要件を実装する方法は、_EnumSet<TChild>_(名前の方が良いかもしれません)のようなクラスから始めることです。ここで、TChildは、階層内のこのレベルの子のタイプです。このクラスには、そのすべての子のコレクションも含まれます(後で説明します)。階層のリーフレベルを表す別のタイプも必要です。非ジェネリックEnumSet

_abstract class EnumSet
{}

abstract class EnumSet<TChild> : EnumSet where TChild : EnumSet
{
    protected IEnumerable<TChild> Children { get; private set; }

    public bool Contains(TChild child)
    {
        return Children.Contains(child);
    }
}
_

次に、階層の各レベルにクラスを作成する必要があります。

_abstract class Root : EnumSet<Kingdom>
{}

abstract class Kingdom : EnumSet<Phylum>
{}

abstract class Phylum : EnumSet
{}
_

そして最後にいくつかの具象クラス:

_class Organisms : Root
{
    public static readonly Organisms Instance = new Organisms();

    private Organisms()
    {}

    public readonly Animalia Animalia = new Animalia();
    public readonly Plantae Plantae = new Plantae();
}

class Plantae : Kingdom
{
    public readonly Anthophyta Anthophyta = new Anthophyta();
}

class Anthophyta : Phylum
{}

class Animalia : Kingdom
{
    public readonly Chordata Chordata = new Chordata();
}

class Chordata : Phylum
{}
_

子は常に親クラスのフィールドであることに注意してください。つまり、Childrenコレクションを埋めるために、リフレクションを使用できます。

_public EnumSet()
{
    Children = GetType().GetFields(BindingFlags.Instance | BindingFlags.Public)
                        .Select(f => f.GetValue(this))
                        .Cast<TChild>()
                        .ToArray();
}
_

このアプローチの1つの問題は、Contains()が常に1レベル下でしか機能しないことです。したがって、Organisms.Instance.Contains(animalia)は実行できますが、.Contains(chordata)は実行できません。これを行うには、特定の階層クラスにContains()のオーバーロードを追加します。例:

_abstract class Root : EnumSet<Kingdom>
{
    public bool Contains(Phylum phylum)
    {
        return Children.Any(c => c.Contains(phylum));
    }
}
_

しかし、これは深い階層では多くの作業になります。


このすべての後、結局は非常に多くの反復的なコードになります。これを修正する1つの方法は、階層を説明するテキストファイルを用意し、T4テンプレートを使用して、それに基づいてすべてのクラスを生成することです。

9
svick

私はこれがあなたがそれを実装できる最善の方法だと思います: https://stackoverflow.com/a/24969525/538387

しかし、ご覧のように、コードがさらに複雑になるため、一般的には良い方法ではありません。

通常の列挙型定義のように、すべての列挙値をフラットに保ち、いくつかの拡張またはヘルパーメソッドを追加することをお勧めします。お気に入り:

public enum Animal
{
    // Mammals
    Cat,
    Dog,
    Bear

    // Birds
    Pigeon,
    Hawk,
    Ostrich

    // Fishes
    Goldfish,
    Trout,
    Shark
};


public static class AnimalExtension
{
    public static bool IsMammal(this Animal animal)
    {
        return animal == Animal.Cat
            || animal == Animal.Dog
            || animal == Animal.Bear;
    }

    public static bool IsBird(this Animal animal)
    {
        ...
    }

    public static bool IsFish(this Animal animal)
    {
        ...
    }
}

だからあなたは簡単にあなたのコードで書くことができます:

Animal animal = GetValueFromASource();

if (animal.IsMammal)
{
    Console.Write("This is a mammal.");
}
1
Tohid

私の解決策は、ビットフラグを使用することです。慣れていない場合は、 ここ を参照してください。一般的な考え方はシンプルですが、それでも列挙型を使用する必要性に沿っています。

より良い作業が不足しているため、クラスの各グループは、ほんの一部にすぎません。だから哺乳類のためにあなたはビットのストレッチを委任するだけです。あなたの場合、あなたはNone(0x0)生物、おそらくカモノハシを持っているでしょう。次に、0x1は猫、0x2は犬、0x4は熊です。哺乳類カテゴリに含まれているかどうかを確認するには、7とのANDを実行し、結果が確認対象の値であることを確認します。

動物界に含まれているかどうかを確認するには、すべてのフラグ付きの値の範囲とAND演算します。

これはスケーラビリティに最適な手法ではありませんが、探している機能である可能性があります。

0
hurricaneMitch