web-dev-qa-db-ja.com

列挙型をいつ使用し、いつ静的メンバーを持つクラスに置き換えるか?

最近、次の(サンプル)列挙が...

enum Color
{
    Red,
    Green,
    Yellow,
    Blue
}

...見た目はよりタイプセーフなクラスに置き換えることができます。

class Color
{
    private Color() { }

    public static readonly Color   Red      = new Color();
    public static readonly Color   Green    = new Color();
    public static readonly Color   Yellow   = new Color();
    public static readonly Color   Blue     = new Color();
}

「タイプセーフ」とは、Colorが列挙型の場合は次のステートメントが機能するが、Colorが上記のクラスの場合は機能しないことを意味します。

var nonsenseColor = (Color)17;    // works if Color is an enum

2つの質問:

1)このパターンに広く受け入れられている名前はありますか(列挙型を型保証クラスで置き換えます)?

2)列挙型を使用する必要があるのはどの場合ですか。また、クラスのほうが適しているのはいつですか。

列挙型は、軽量の状態情報に最適です。たとえば、カラー列挙型(青を除く)は、信号機の状態を照会するのに適しています。トゥルーカラーは、色の概念全体とそのすべての手荷物(アルファ、色空間など)に関係なく、どの状態がライトインであるかは関係ありません。また、列挙型を少し変更して信号機の状態を表します:

[Flags()]
public enum LightColors
{
    unknown = 0,
    red = 1,
    yellow = 2,
    green = 4,
    green_arrow = 8
}

現在のライトの状態は次のように設定できます。

LightColors c = LightColors.red | LightColors.green_arrow;

そして次のようにクエリします:

if ((c & LightColors.red) == LightColors.red)
{
    //Don't drive
}
else if ((c & LightColors.green_arrow) == LightColors.green_arrow)
{
    //Turn
}

静的クラスcolorメンバーは、追加機能なしでこの複数の状態をサポートできます。

ただし、静的クラスメンバーは、一般的に使用されるオブジェクトにとって素晴らしいものです。 System.Drawing.Colorメンバーは、わかりにくいコンストラクターを持つ既知の名前の色を表すため、優れた例です(16進数の色を知らない限り)。列挙型として実装されている場合は、値を色として使用するたびに、次のようにする必要があります。

colors c = colors.red;
switch (c)
{
    case colors.red:
        return System.Drawing.Color.FromArgb(255, 0, 0);
        break;
    case colors.green:
        return System.Drawing.Color.FromArgb(0,255,0);
        break;
}

したがって、列挙型があり、オブジェクトを派生させるために常にswitch/case/if/else/whateverを実行していることがわかった場合は、静的クラスメンバーを使用することをお勧めします。何かの状態のみを照会する場合は、列挙型を使用します。また、安全でない方法でデータを渡す必要がある場合、列挙型はおそらくオブジェクトのシリアル化されたバージョンよりも存続します。

編集: @ stakx、@ Antonの投稿に反応して、あなたも何か重要なものに出会ったと思います。

消費者の立場から見ると、私はSystem.Drawing.Color静的クラスメンバーをすべて記述する必要があるよりも、はるかに優先するでしょう。しかし、プロデューサーの観点からすると、それをすべて記述する必要があるのは面倒です。したがって、他の人があなたのコードを使用する場合、静的クラスメンバーを使用することで、書き込み/テスト/デバッグに10倍の時間がかかる場合でも、それらを大幅に節約できます。ただし、それだけの場合は、列挙型を使用し、必要に応じてキャスト/変換する方が簡単な場合があります。

27
Chris Haas

私は実際にこのようなことを仕事でかなり苦労しています。

John Bによって投稿された例Jon Skeetのブログ記事「C#の拡張列挙型」 に基づいていました。

醜いLOTがなければ適切に機能しなかったのは、基本列挙型クラスを派生させ、派生型の静的フィールドを追加して列挙型を拡張することでした。

そのブログに投稿されている基本を確認すると、派生クラスは、深刻なタイピングの問題がある共通の値のセットを共有し、特定の列挙基本クラスが2つの異なる他のクラスで派生すると、それらの値セットは同じコレクションも。

つまり、DerivedEnumClass1の変数を作成し、その列挙コレクションの値を実際にはBaseEnumClass1である変数をその変数に格納する必要があり、回避策はありません。

もう1つの問題は、よく使用するXmlシリアル化でした。ビジネスオブジェクトの列挙変数のデータ型として2つのジェネリッククラスを使用することで回避しました。1つは単一の列挙値を表し、もう1つはフラグ列挙動作を模倣するために使用した一連の列挙値を表しました。それらは、それらが表すプロパティに格納された実際の値のxmlシリアル化および逆シリアル化を処理しました。この方法でビットフラグの動作を再現するには、いくつかの演算子のオーバーロードが必要でした。

これらは、私が遭遇した主要な確信の一部です。

全体として、私は最終結果に非常に満足していません。そこにいくつかの臭いものがありますが、今のところそれでうまくいきます。

チーフアーキテクトがまだ試してみてそれを機能させることに決めたのは、実際のクラスを作成し、汎用または特定のあらゆる種類の動作でそれらを拡張できる可能性があるためです。これまでのところ、拡張メソッドを提供することはできなかったでしょうが、何かに遭遇するかもしれません。

ここにコードはありません。週末にそのコードにアクセスすることはできません。そうでなければ、どこに行ったかを示すことができます。あまりきれいではないけど...:o

3
Anton

その間に私が見つけたいくつかのこと、他の誰かが興味があるなら:

  • switchブロックは機能しません enum-as-class。

  • ユーザーempiは、上記のenum-as-classサンプルのJava enumsとの類似性について言及しました。 Java世界、Typesafe Enumと呼ばれる認識されたパターンがあります;明らかにこのパターンはJoshua Blochの本Effective Java

2
stakx

一緒にしたり組み合わせたりする可能性のあるあいまいなビットフラグ値が必要なレガシーコードと相互運用する必要がある場合は、クラス定数を使用します。この場合、列挙型は次のようになります。

public enum Legacy : ushort
{
  SomeFlag1 = 0x0001,
  SomeFlag2 = 0x0002,
  // etc...
}

その後、列挙型を適切な値に変換するためにキャストが必要になるため、P/Invokeでのマーシャリングは読みにくくなります。

それがconstクラス変数である場合、キャストは必要ありません。

1
cmw

どちらの方法も有効です。ケースごとに選択してください。

列挙型がフラグとしてビット操作をサポートすることを追加できます(このセマンティクスをサポートし、列挙型からきれいな文字列を生成するための[Flags]属性さえあります)。

非常によく似たリファクタリングには、Enumを戦略パターンに置き換えるという名前があります。まあ、あなたの場合、あなたの色は受動的で戦略のように振る舞わないので、完全ではありません。しかし、それを特別な場合と考えてみませんか?

1
Alex P

Java 1.5およびJavaの列挙型のネイティブサポート)の前にリリースされたJoshua Blochによる「Effective Java」のオリジナル版であるTypesafe Enumパターン。残念ながら、本の最新リリースはJava> 1.5を対象としているため、代わりにJava enumsを使用しています。

次に、Javaではintからenumにキャストできません。

Color nonsenseColor = (Color)17; // compile-error

他の言語は話せませんが。

0
organicveggie