EnumにAttributeを付与して、Attributeを取得する方法です。
狙いとしては下記のようにenumにAttributeを付与しておき、付属情報を取得するというものです。
列挙型でありつつ、もう少し情報を付与したいというケースです。
private enum Fruits { [EnumLabel("りんご")] Apple, [EnumLabel("ぶどう")] Grape } Console.WriteLine(EnumLabelAttribute.GetLabel(Fruits.Apple)); // りんご
他の方の記事を参考にしながら自分にあった形にすることにしました。ほぼ自分への備忘録です。
- トリッキーな印象を受けるのでExtensionにしない
- キャッシュする
カスタム属性の実装は、これまでしたことなかったので、属性ってこういうことね、と理解が深まります。
実装コード
/// <summary> /// <see cref="Attribute"/>をキャッシュするクラスです。 /// </summary> internal static class EnumAttributeCache<TAttribute> where TAttribute : Attribute { private static readonly ConcurrentDictionary<Enum, TAttribute> Cache = new ConcurrentDictionary<Enum, TAttribute>(); /// <summary> /// <see cref="ConcurrentDictionary.GetOrAdd"/>を呼び出します。 /// </summary> internal static TAttribute GetOrAdd(Enum enumKey, Func<Enum, TAttribute> valueFactory) => Cache.GetOrAdd(enumKey, valueFactory); } [System.AttributeUsage(System.AttributeTargets.Field, AllowMultiple = false, Inherited = false)] public class EnumLabelAttribute : System.Attribute { public string Label; /// <inheritdoc /> public EnumLabelAttribute(string label) { Label = label; } /// <summary> /// <see cref="Label"/>を取得します。 /// </summary> /// <param name="value"></param> /// <returns></returns> public static string GetLabel(Enum value) { return EnumAttributeCache<EnumLabelAttribute>.GetOrAdd(value, _ => GetLabelInternal(value)).Label; } /// <summary> /// キャッシュされていない初回時に<see cref="Label"/>を取得します。 /// </summary> /// <param name="value"></param> /// <returns></returns> private static EnumLabelAttribute GetLabelInternal(Enum value) { var fieldInfo = value.GetType().GetField(value.ToString()); var attributes = fieldInfo.GetCustomAttributes(typeof(EnumLabelAttribute), false) .Cast<EnumLabelAttribute>().ToList(); if (!attributes.Any()) { throw new InvalidOperationException($"[{value}]からはAttributeが取得できませんでした。"); } return attributes.First(); } }
テスト・呼び出しコード
[TestFixture] public class EnumLabelAttributeTest { private const string NoneLabelName = "なし"; private enum TestEnum { [EnumLabel(NoneLabelName)] None, // あえてAttributeを付与しない NotLabel } /// <summary> /// <see cref="EnumLabelAttribute.GetLabel"/>をテストします。 /// </summary> [Test] public void GetLabel() { Assert.AreEqual(EnumLabelAttribute.GetLabel(TestEnum.None),NoneLabelName,"初回取得"); Assert.AreEqual(EnumLabelAttribute.GetLabel(TestEnum.None),NoneLabelName,"二度目にキャッシュから取得"); } /// <summary> /// <see cref="EnumLabelAttribute"/>が指定されていない値に対して、<see cref="EnumLabelAttribute.GetLabel"/>が実行された異常系をテストします。 /// </summary> [Test] public void GetLabelNoneAttribute() { Assert.Catch<InvalidOperationException>(() => { EnumLabelAttribute.GetLabel(TestEnum.NotLabel); }); } }