65.9K
CodeProject 正在变化。 阅读更多。
Home

枚举描述的字典

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (8投票s)

2014年4月19日

CPOL

3分钟阅读

viewsIcon

32451

downloadIcon

254

一个用于存储枚举字段上 Description 属性提供的字符串描述的字典类。

引言

在这里,我介绍了一个基于字典的简单类,即 EnumDescripionDictionary<TEnum>,用于存储 Enum 类型的成员的描述性文本。 本文允许使用 Description 属性来提供此文本,但未来的一篇文章将稍微扩展该类,以允许任何属性类型为枚举字段提供描述。

背景

基于 Enum 的类型的一个核心目的是使用自然语言的描述性,而不是纯数字的模糊性,以使开发人员的生活更轻松。 但是,用于枚举字段的单字名称通常对于最终用户来说远远不够描述性,并且我们希望为枚举字段关联更长、更易于理解的描述,通常用于 UI 中。 例如,ComboBox 项目可以具有来自枚举字段的 Value 属性,以及来自该字段描述的 Text 属性。

多功能的 Description 属性可用于装饰许多代码工件,包括枚举字段,以提供更多信息性描述。 但是,访问属性提供的描述需要使用反射,这是我们在需要描述时真正需要避免的事情。 我在这里描述的类允许创建一个 Enum 类型的描述字典一次,以便在我们需要描述的任何时候使用。

使用代码

Dictionary<TKey, TValue> 派生,这个类除了下面的构造函数代码外,不需要更多(实用性)。 为了不切实际的完美,这个字典应该在所有方面都是只读的,并且字典中每个枚举成员的描述也应该是只读的。 为此,我本来需要实现 IDictionary<TKey, TValue>,这是一个相当大的接口,而不是仅仅从 Dictionary<TKey, TValue> 派生。

/// <summary>
/// Provides a dictionary of descriptions of arbitrary length for fields of any enum type.
/// </summary>
/// <typeparam name="TEnum">The <see cref="Enum"/> type to build a descriptions dictionary from.</typeparam>
/// <remarks>
/// The class looks for a <see cref="DescriptionAttribute"/> on each field of the enum, and if found, it 
/// uses the value provided by that. If not found, it simply uses the field name itself, as it does if 
/// the value of the <see cref="DescriptionAttribute.Description"/> property is empty. The dictionary 
/// key is the enum field value itself.
/// </remarks>
public class EnumDescripionDictionary<TEnum> : Dictionary<TEnum, string> where TEnum : struct, IComparable, IConvertible, IFormattable
{
  public EnumDescripionDictionary()
  {
    if (!typeof(TEnum).IsEnum)
    {
      // There is no BCL exception suitable for invalid generic type parameters.
      throw new NotSupportedException("Generic parameter T must be of type Enum.");
    }

    // These binding flags exclude the hidden, generated instance field named 'value__'. 
    var fields = typeof(TEnum).GetFields(BindingFlags.Public | BindingFlags.Static);
    foreach (var field in fields)
    {
      var descAtt = field.GetCustomAttribute<DescriptionAttribute>();
      if (descAtt != null)
      {
        var desc = descAtt.Description;
        if (!string.IsNullOrEmpty(desc))
        {
          Add((TEnum)Enum.Parse(typeof(TEnum), field.Name), desc);
          continue;
        }
      }
      Add((TEnum)Enum.Parse(typeof(TEnum), field.Name), field.Name);
    }
  }

  /// <summary>
  /// This method hides the <see cref="Remove"/> method of the base <see cref="Dictionary{TKey, TValue}"/> class 
  /// to prevent the removal of items. As this dictionary describes an <see cref="Enum"/> type, its contents 
  /// may not be changed at runtime.
  /// </summary>
  /// <param name="key">Key of the dictionary item to be removed.</param>
  /// <remarks>
  /// It is not necessary to hide the <see cref="Dictionary{TKey, TValue}.Add"/> method, as using any of the enum
  /// values as a key for add will cause an <see cref="ArgumentException"/> because of the duplicate key.
  /// </remarks>
  public new void Remove(TEnum key)
  {
    throw new InvalidOperationException(string.Format("Items may not be removed from this dictionary as type '{0}' has not changed.", typeof(TEnum).Name));
  }
}

代码相当不言自明,并且使用以下测试枚举,我将给出一个用法示例

internal enum TestEnum
{
  [Description("Not Operational")]
  Nop,
  Installation,
  [Description("System Set-up")]
  SystemSetup,
  [Description("Import / Export")]
  ImportExport,
  Configuration,
  [Description("Help and Documentation")]
  Help,
  Uninstall
}  

一个构建并演示 EnumDescripionDictionary<TestEnum> 用法的示例程序

class Program
{
  static void Main(string[] args)
  {
    var dict = new EnumDescripionDictionary<TestEnum>();
    Console.WriteLine();
    Console.WriteLine("Value: {0}\tDesc: '{1}'", TestEnum.Nop, dict[TestEnum.Nop]);
    Console.WriteLine("Value: {0}\tDesc: '{1}'", TestEnum.Configuration, dict[TestEnum.Configuration]);
    Console.WriteLine();
    Console.WriteLine("Press Enter to exit.");
    Console.ReadLine();
  }
} 

这会产生以下输出:

关注点

奇怪而精彩的泛型类型参数约束被使用,因为 .NET 没有提供任何原生约束来说明类似 where TKey: Enum 的东西。

然后我发现了 Jon Skeetunconstrained-melody,“使用“无效”约束的 C# 实用程序库”。它允许我替换

public class EnumDescripionDictionary<TEnum> : Dictionary<TEnum, string> where TEnum : struct, IComparable, IConvertible, IFormattable  

public class EnumDescripionDictionary<TEnum> : Dictionary<TEnum, string> where TEnum : IEnumConstraint 

它还提供了一系列可爱的 Enum 实用程序,这些实用程序远远超过了我上面所做的,并且在很大程度上使其变得多余。

编译后,在枚举字段中突然出现一个名为“value__”的新公共字段确实令人惊讶,但正如我所评论的那样,正确的绑定标志很容易隐藏这个字段。 这个StackOverflow 回答提供了更多解释

JIT 编译器需要一个描述其布局的value类型定义,当它被装箱时。 它们中的大多数都被烘焙到 mscorlib 中,例如 System.Int32。 enum 关键字允许你创建一个新的值类型。 因此,编译器必须在元数据中为其提供定义。 这就是你正在查看的。 你将看到每个枚举成员的静态字段,由 ToString() 使用。 还有一个实例字段名为 value__,用于存储枚举值。 关键点是,这只存在于枚举值的装箱版本中。

下一篇

EnumDescripionDictionary<TEnum> 类的扩展,它将从你可能希望使用的任何自定义属性的任何属性中提取描述,而不是 Description

© . All rights reserved.