やりたいこと
Enumのフィールド名と表示名を分離したい。
最終的な目標として、Sushi.Tako
というenumを表示する文字として"タコ"
を割り当てたい。かつ汎用性のある構造にしたい。多言語によるローカライズにも対応したい。今回はそういうお話です。
2019/2/2 追記
ローカライズについて → ちゃんと多言語化できるよう追加記事書きました
が、悲しいかなそんなものはデフォルトで用意されていない。
ないものは書く。
以下のようなEnumがあるとします。
public enum Sushi { Tako, Ika, Ebi }
これをデータとして保持している分には何も問題ないのですが、このデータを画面に表示したい時に普通にToStinrg()すると"Tako"
,"Ika"
のように要素名が直に表示されています。困ります。
C#ではクラス名やフィールド名などに使える文字は、一部の予約語や記号を除いてなんでも使えます。すごい。
public enum Sushi { タコ, イカ, エビ }
非常にやっつけ仕事感満載の上記のenumですが、普通にコンパイルできる上にToString()したらちゃんと日本語で"タコ"
になります。
やったね。
やってねえよこのタコ野郎
茶番はこの程度にして本題に入りましょう。
本題
属性を使えばこの問題を解決できます。EnumDisplayNameという属性を作ります。
属性定義
[AttributeUsage(AttributeTargets.Field)] public class EnumDisplayNameAttribute : Attribute { /// <summary>表示名</summary> public string Name { get; set; } /// <summary>enum表示名属性</summary> /// <param name="name">表示名</param> public EnumDisplayNameAttribute(string name) { Name = name; } }
public enum Sushi { [EnumDisplayName("タコ")] Tako, [EnumDisplayName("イカ")] Ika, [EnumDisplayName("エビ")] Ebi }
属性を使ったことでenumのフィールドそのものにメタ情報として表示名を付与することができました。Sushi.Tako
と"タコ"
が別々の場所に定義されていて後から対応づける、といったやり方より、見た目的にもスッキリ1つのenumの定義の中に収まっていていい感じです。
あとはこの属性を取り出す処理を書けばいいだけです。 使用側の見た目的にもいいので拡張メソッドとして実装してみることにしましょう。
拡張メソッド定義
public static class EnumExtension { public static string GetEnumDisplayName<T>(this T enumValue) { var field = typeof(T).GetField(enumValue.ToString()); var attrType = typeof(EnumDisplayNameAttribute); var attribute = Attribute.GetCustomAttribute(field, attrType); return (attribute as EnumDisplayNameAttribute)?.Name; } }
表示名使用側
// Sushi型のenum値 var sushi = Sushi.Tako; // enum値から表示名を取り出す var displayName = sushi.GetEnumDisplayName();
日本語のリテラルでハードコーディングではなく、多言語化などでリソースから表示名を設定したい場合なども、
フィールドの属性の部分をリソース値にするだけでいいので汎用性も多言語化もバッチリですね。
(追記)全然バッチリじゃなかったので追加記事書きました。
余談
System.ComponentModel
名前空間にDisplayNameAttribute
という表示名属性がデフォルトで用意されています。
が、プロパティやクラスにはつけることができるのですが、今回の目的のenumのフィールドにこれをつけることができません。なのでわざわざ自作のEnumDisplayName属性を作ったのですね。
他言語へのローカライズ (追記)
他言語のローカライズ、リソースに普通に書けばいけると思い本記事を書きましたが、 検証した結果できませんでした。以下のようにコンパイルエラーになります。
よく考えればその通りです。リソース値は定数リテラルではないので属性引数として渡せません。 このままではあまり嬉しくないので対策を考えます。。。
ちゃんとローカライズできるよう追加記事書きました