枚举描述的字典






4.96/5 (8投票s)
一个用于存储枚举字段上 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 Skeet 的 unconstrained-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
。