web-dev-qa-db-ja.com

C#列挙値の取得

私は以下を含む列挙型を持っています(例えば):

  • イギリス、
  • アメリカ、
  • フランス、
  • ポルトガル

私のコードではCountry.UnitedKingdomを使用していますが、値を[〜#〜] uk [〜#〜]に割り当てたい場合は文字列たとえば。

これは可能ですか?

30
neildeadman

最初に文字列に列挙値を割り当てることはできません。 ToString()を呼び出す必要があります。これにより、Country.UnitedKingdomが「UnitedKingdom」に変換されます。

2つのオプションがそれ自体を示唆しています。

  • Dictionary<Country, string>を作成します
  • Switchステートメント
  • 各値を属性で装飾し、それをリフレクションでロードします

それらのそれぞれについてのコメント...

Dictionary<Country,string>のサンプルコード

using System;
using System.Collections.Generic;

enum Country
{
    UnitedKingdom, 
    UnitedStates,
    France,
    Portugal
}

class Test
{
    static readonly Dictionary<Country, string> CountryNames =
        new Dictionary<Country, string>
    {
        { Country.UnitedKingdom, "UK" },
        { Country.UnitedStates, "US" },
    };

    static string ConvertCountry(Country country) 
    {
        string name;
        return (CountryNames.TryGetValue(country, out name))
            ? name : country.ToString();
    }

    static void Main()
    {
        Console.WriteLine(ConvertCountry(Country.UnitedKingdom));
        Console.WriteLine(ConvertCountry(Country.UnitedStates));
        Console.WriteLine(ConvertCountry(Country.France));
    }
}

ConvertCountryのロジックを拡張メソッドに入れたいと思うかもしれません。例えば:

// Put this in a non-nested static class
public static string ToBriefName(this Country country) 
{
    string name;
    return (CountryNames.TryGetValue(country, out name))
        ? name : country.ToString();
}

次に、次のように書くことができます。

string x = Country.UnitedKingdom.ToBriefName();

コメントで述べたように、デフォルトの辞書比較プログラムにはボクシングが含まれますが、これは理想的ではありません。一回限りは、それがボトルネックであることがわかるまで、私はそれと一緒に暮らしていました。複数の列挙型に対してこれを行う場合は、再利用可能なクラスを作成します。

Switchステートメント

私は同意します yshuditeluの答え 比較的少数のケースでswitchステートメントを使用することを提案します。ただし、それぞれのケースが1つのステートメントになるため、コードをコンパクトで読みやすくするために、この状況に合わせてコーディングスタイルを個人的に変更します。

public static string ToBriefName(this Country country) 
{
    switch (country)
    {
        case Country.UnitedKingdom:  return "UK";
        case Country.UnitedStates:   return "US";
        default:                     return country.ToString();
    }
}

大きくなりすぎずにこれにケースを追加でき、列挙値から戻り値に目を向けるのは簡単です。

DescriptionAttribute

DescriptionAttributeのコードが再利用可能であるという点 Rado made は良いことですが、その場合、値を取得する必要があるたびにリフレクションを使用しないことをお勧めします。私はおそらく、ルックアップテーブルを保持するための汎用静的クラスを作成します(おそらくコメントに記載されているように、カスタム比較ツールを使用してDictionary)。拡張メソッドはジェネリッククラスで定義できないため、おそらく次のようになります。

public static class EnumExtensions
{
    public static string ToDescription<T>(this T value) where T : struct
    {
        return DescriptionLookup<T>.GetDescription(value);
    }

    private static class DescriptionLookup<T> where T : struct
    {
        static readonly Dictionary<T, string> Descriptions;

        static DescriptionLookup()
        {
            // Initialize Descriptions here, and probably check
            // that T is an enum
        }

        internal static string GetDescription(T value)
        {
            string description;
            return Descriptions.TryGetValue(value, out description)
                ? description : value.ToString();
        }
    }
}
53
Jon Skeet

私は列挙型で DescriptionAttribute を使用することを好みます。次に、次のコードを使用して、列挙型からその説明を取得できます。

enum MyCountryEnum
{    
    [Description("UK")]
    UnitedKingdom = 0,    

    [Description("US")]
    UnitedStates = 1,    

    [Description("FR")]
    France = 2,    

    [Description("PO")]
    Portugal = 3
}

public static string GetDescription(this Enum value)
{
    var type = value.GetType();

    var fi = type.GetField(value.ToString());

    var descriptions = fi.GetCustomAttributes(typeof(DescriptionAttribute), false) as DescriptionAttribute[];

    return descriptions.Length > 0 ? descriptions[0].Description : value.ToString();
}

public static SortedDictionary<string, T> GetBoundEnum<T>() where T : struct, IConvertible
{
    // validate.
    if (!typeof(T).IsEnum)
    {
        throw new ArgumentException("T must be an Enum type.");
    }

    var results = new SortedDictionary<string, T>();

    FieldInfo[] fieldInfos = typeof(T).GetFields();

    foreach (var fi in fieldInfos)
    {

        var value = (T)fi.GetValue(fi);
        var description = GetDescription((Enum)fi.GetValue(fi));

        if (!results.ContainsKey(description))
        {
            results.Add(description, value);
        }
    }
    return results;
}

そして、バインドされた列挙型リストを取得するには、

GetBoundEnum<MyCountryEnum>()

単一の列挙型の説明を取得するには、次のような拡張メソッドを使用します

string whatever = MyCountryEnum.UnitedKingdom.GetDescription();
23
Scott Ivey

拡張メソッドpublic static string ToShortString(this Country country)を作成できます。この方法では、Jonが提案するように静的辞書を使用することも、単にswitchcaseを実行することもできます。

例:

public static class CountryExtensions
{
    public static string ToShortString( this Country target )
    {
        switch (target) {
            case Country.UnitedKingdom:
                return "UK";
            case Country.UnitedStates:
                return "US";
            case Country.France:
                return "FR";
            case Country.Portugal:
                return "PT";
            default:
                return "None";
        }
    }
}
15
Timothy Carter

擬似コード:

enum MyCountryEnum
{
    UnitedKingdom = 0,
    UnitedStates = 1,
    France = 2,
    Portugal = 3,
}

string[] shortCodes = new string[] {"UK", "US", "FR", "PO"};


MyCountryEnum enumValue = MyCountryEnum.UnitedKingdom;
string code = shortCodes[enumValue];
6
rein

言及されていないもう1つの可能性は、次のようなものです。

public class Country
{
    public static readonly Country UnitedKingdom = new Country("UK");
    public static readonly Country UnitedStates = new Country("US");
    public static readonly Country France = new Country("FR");
    public static readonly Country Protugal = new Country("PT");

    private Country(string shortName)
    {
        ShortName = shortName;
    }

    public string ShortName { get; private set; }
}

この時点から、さらにプロパティを追加できますが、クラスに追加する量と追加する静的メンバーの数に注意してください。追加するメモリの肥大化により、価値がなくなる可能性があります。

この戦略が最善のアプローチである場合は多くないと思いますが、本質的に列挙型として扱いたいものにプロパティまたは属性を追加しようとするときに注意するオプションです。

3
Timothy Carter

このプロジェクトの仕事をしばらくやめなければならなかったのですが、戻ってきて、インスピレーションを得ました。

列挙型ではなく、次のような新しいクラスを作成しました。

public class Country
{
    public const string UnitedKingdom = "UK";
    public const string France = "F";
}

このようにして、コードでCountry.UnitedKingdomを使用でき、値「UK」が使用されます。

代替ソリューションとしてこの回答を投稿しています。

ニール

3
neildeadman

DescriptionAttribute を使用するだけです

列挙値の文字列表現を取得するだけでよい場合は、辞書を作成する必要はありません。これを参照してください

[編集]ああ...説明を取得するのに役立つ共通のutilクラスが1つだけ必要なので、辞書よりも再利用可能であることに言及するのを忘れました。次に列挙値を追加するときに、DescriptionAttributeを追加するだけです。または、同じ要件で新しい列挙型を作成します。辞書/スイッチソリューションでは、保守が難しく、列挙型が多数あると面倒になります。

2
Rado

Scott Iveyの回答に編集を送信しようとしましたが、却下されました。別の回答があります。私の比較的マイナーな編集:

1)System.ArgumentException: Field 'value__' defined on type 'MyClass.EnumHelperTest+MyCountryEnum' is not a field on the target object which is of type 'System.Reflection.RtFieldInfo'.のAlexのエラーを修正しました ここ から取得しました。

2)returnを追加して、実際にコピーして貼り付けることができ、機能するようにしました。

3)SortedDictionaryは常にキー(この場合は文字列Description)でソートするため、SortedDictionaryをDictionaryに変更しました。説明をアルファベット順に並べる理由はありません。実際、それをソートすると、列挙型の元の順序が破壊されます。辞書は列挙型の順序も保持しませんが、少なくとも、SortedDictionaryのように順序を意味するものではありません。

enum MyCountryEnum
{    
    [Description("UK")]
    UnitedKingdom = 0,    

    [Description("US")]
    UnitedStates = 1,    

    [Description("FR")]
    France = 2,    

    [Description("PO")]
    Portugal = 3
}

public static string GetDescription(this Enum value)
{
    var type = value.GetType();

    var fi = type.GetField(value.ToString());

    var descriptions = fi.GetCustomAttributes(typeof(DescriptionAttribute), false) as DescriptionAttribute[];

    return descriptions.Length > 0 ? descriptions[0].Description : value.ToString();
}

public static Dictionary<string, T> GetBoundEnum<T>() where T : struct, IConvertible
{
    // validate.
    if (!typeof(T).IsEnum)
    {
        throw new ArgumentException("T must be an Enum type.");
    }

    var results = new Dictionary<string, T>();

    FieldInfo[] fieldInfos = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Static);

    foreach (var fi in fieldInfos)
    {

        var value = (T)fi.GetValue(fi);
        var description = GetDescription((Enum)fi.GetValue(fi));

        if (!results.ContainsKey(description))
        {
            results.Add(description, value);
        }
    }
    return results;
}
2
DharmaTurtle

列挙型を見るたびに、コードをリファクタリングする必要があると感じます。 Countryクラスを作成し、メソッドを追加して、回避しようとしているいくつかの障害を実行してみませんか。列挙型に値を割り当てることは、さらに大きなコードの臭いです。

なぜ反対票を投じるのですか?列挙型を使用するよりも多態的なアプローチを使用する方が優れていることは、かなり広く受け入れられていると思います。代わりにValueObjectデザインを使用できる場合、列挙型を使用する理由はありません。

このトピックに関する優れたブログ投稿は次のとおりです。 http://devpinoy.org/blogs/cruizer/archive/2007/09/12/enums-are-evil.aspx

1
Bryan Rowe
var codes = new Dictionary<Country, string>() 
        { { Country.UnitedKingdom, "UK" },
        { Country.UnitedStates, "US" },
        { Country.France, "FR" } };
Console.WriteLine(codes[Country.UnitedStates]);
0
BlackTigerX

次のソリューションが機能します(コンパイルして実行します)。 2つの問題があります。

  1. 列挙型が同期していることを確認する必要があります。 (自動テストでそれを行うことができます。)

  2. 列挙型は.NETではタイプセーフではないという事実に頼ることになります。

    enum Country
    {
        UnitedKingdom = 0,
        UnitedStates = 1,
        France = 2,
        Portugal = 3
    }
    
    enum CountryCode
    {
        UK = 0,
        US = 1,
        FR = 2,
        PT = 3
    }
    
    void Main()
    {
        string countryCode = ((CountryCode)Country.UnitedKingdom).ToString();
        Console.WriteLine(countryCode);
        countryCode = ((CountryCode)Country.Portugal).ToString();
        Console.WriteLine(countryCode);
    }
    
0
Klinger