.NET枚举类型优化探讨(三)

.NET枚举类型优化探讨(二)中我们探讨了“使用类或结构来替代部分枚举类型”的方案并试图进行进一步的重构和优化,但是发现有很多限制,不但没有完成重构,且发现了很多该方案不适用的地方和缺陷。在某些情况下,这种方案会对生产带来相反的作用,所以在文中我建议不要滥用。今天我们来探讨一下使用.NET中的Attribute特性来扩展.NET枚举值的方案。

前面我们提到,.NET中的枚举类型的成员定义约束是很严格的,只能在里面增删字段,而不能定义方法、属性等高级成员。当枚举类型与单个数值常量绑定的时候,只需要在字段后面使用“=”赋值即可,但是如果我们需要绑定非数值常量或想绑定更多的信息时该怎么办呢?

在编写本文之前,我综合网上现有的资料,大概总结了一下,有如下方案可供参考:

  1. 如果想绑定1条中文常量,那么字段直接声明为中文就可以了。理由是:.NET支持Unicode编码,而且该方案不需要任何额外的编码;
  2. 可以使用Dictionary<,>字典类。理由是:反正枚举值都是唯一的,可以作为键,而中文常量可以作为值,而且是泛型,效率高;
  3. 还是方案2,只不过当需要绑定多个常量的时候,可以考虑将键值的中文替换为一个自定义对象;
  4. 使用Attribute特性来扩展,但缺点是需要反射;

前三种方案都很简单,网上也有其他朋友提供的资料,今天我们只讨论最后一种。这里要牵扯到一些概念,请童鞋们先自行复习一下:Attribute特性反射位域操作等。

下面我们来看一段代码实例,这里演示了如何使用Attribute特性来扩展枚举类型:

 1 /// <summary>
2 /// 表示日志类型的枚举值。
3 /// </summary>
4 [Serializable]
5 public enum LogType
6 {
7 /// <summary>
8 /// 信息。
9 /// </summary>
10 [ColoredEnumDescription("信息", "#000000")]
11 Info = 0,
12
13 /// <summary>
14 /// 警告。
15 /// </summary>
16 [ColoredEnumDescription("警告", "#0000FF")]
17 Warnning = 1,
18
19 /// <summary>
20 /// 错误。
21 /// </summary>
22 [ColoredEnumDescription("错误", "#FF0000")]
23 Error = 2
24 }

看到如上代码片段,大家可以猜测一下其用途:基本上就是在记录错误日志的时候,以中文“错误”做个注释标记,且将信息标记为红色。

使用实例:

 1 var logTypeVictor = new EnumVictor<LogType, ColoredEnumDescriptionAttribute>();
2
3 var title = logTypeVictor[LogType.Error].Description;
4 var color = logTypeVictor[LogType.Error].ForeColor;
5 var html = logTypeVictor[LogType.Error].ToHTML("http://www.cnblogs.com/ymind/");
6
7 Trace.WriteLine(title);
8 Trace.WriteLine(color);
9 Trace.WriteLine(html);
10
11 /*
12 输出结果:
13
14 错误
15 #FF0000
16 <a href="http://www.cnblogs.com/ymind/" style="color:#FF0000;">错误</a>
17 */

可以看到,使用起来也比较方便。这里就使用了一个自定义特性“ColoredEnumDescriptionAttribute”。在.NET中,Attribute(即特性)的所有派生类都建议以“Attribute”结尾,而在引用时其末尾的“Attribute”是可以省略不写的,因此“ColoredEnumDescriptionAttribute”又被写作“ColoredEnumDescription”。那么,我们再来看看这个“ColoredEnumDescription”的源代码:

View Code
  1 /// <summary>
2 /// 指定带有颜色注释信息的枚举值的说明。
3 /// </summary>
4 [AttributeUsage(AttributeTargets.Enum | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
5 public sealed class ColoredEnumDescriptionAttribute : EnumDescriptionAttribute
6 {
7 /// <summary>
8 /// 初始化 <see cref="ColoredEnumDescriptionAttribute"/> 类的新实例并带有说明。
9 /// </summary>
10 /// <param name="description">说明文本。</param>
11 public ColoredEnumDescriptionAttribute(string description) : this(description, null) { }
12
13 /// <summary>
14 /// 初始化 <see cref="ColoredEnumDescriptionAttribute"/> 类的新实例并带有说明。
15 /// </summary>
16 /// <param name="description">说明文本。</param>
17 /// <param name="foreColor">前景色。</param>
18 public ColoredEnumDescriptionAttribute(string description, string foreColor) : this(description, foreColor, null) { }
19
20 /// <summary>
21 /// 初始化 <see cref="ColoredEnumDescriptionAttribute"/> 类的新实例并带有说明。
22 /// </summary>
23 /// <param name="description">说明文本。</param>
24 /// <param name="foreColor">前景色。</param>
25 /// <param name="backColor">背景色。</param>
26 public ColoredEnumDescriptionAttribute(string description, string foreColor, string backColor) : this(description, foreColor, backColor, null) { }
27
28 /// <summary>
29 /// 初始化 <see cref="ColoredEnumDescriptionAttribute"/> 类的新实例并带有说明。
30 /// </summary>
31 /// <param name="description">说明文本。</param>
32 /// <param name="foreColor">前景色。</param>
33 /// <param name="backColor">背景色。</param>
34 /// <param name="id">指定 HTML 标签中的 id 特性字段的值。</param>
35 public ColoredEnumDescriptionAttribute(string description, string foreColor, string backColor, string id) : this(description, foreColor, backColor, id, null) { }
36
37 /// <summary>
38 /// 初始化 <see cref="ColoredEnumDescriptionAttribute"/> 类的新实例并带有说明。
39 /// </summary>
40 /// <param name="description">说明文本。</param>
41 /// <param name="foreColor">前景色。</param>
42 /// <param name="backColor">背景色。</param>
43 /// <param name="id">指定 HTML 标签中的 id 特性字段的值。</param>
44 /// <param name="target">指定 HTML 标签中的 target 特性字段的值。</param>
45 public ColoredEnumDescriptionAttribute(string description, string foreColor, string backColor, string id, string target) : base(description)
46 {
47 this.ForeColor = foreColor ?? String.Empty;
48 this.BackColor = backColor ?? String.Empty;
49 this.Id = id ?? String.Empty;
50 this.Target = target ?? String.Empty;
51 }
52
53 /// <summary>
54 /// 获取或设置前景色。
55 /// </summary>
56 public string ForeColor { get; set; }
57
58 /// <summary>
59 /// 获取或设置背景色。
60 /// </summary>
61 public string BackColor { get; set; }
62
63 /// <summary>
64 /// 获取或设置 HTML 标签中的 id 特性字段的值。
65 /// </summary>
66 public string Id { get; set; }
67
68 /// <summary>
69 /// 获取或设置 HTML 标签中的 target 特性字段的值。
70 /// </summary>
71 public string Target { get; set; }
72
73 /// <summary>
74 /// 返回当前对象的 HTML 表示形式。
75 /// </summary>
76 /// <param name="baseUrl">基础链接地址。可以为空。</param>
77 /// <param name="tagName">标签名称。</param>
78 /// <returns>返回 <see cref="System.String"/></returns>
79 public string ToHTML(string baseUrl = null, string tagName = "span")
80 {
81 var html = new StringBuilder();
82
83 html.Append('<');
84
85 if (String.IsNullOrWhiteSpace(baseUrl)) html.Append(tagName);
86 else
87 {
88 tagName = "a";
89
90 html.Append(tagName);
91 html.Append(@" href=""");
92 html.Append(baseUrl);
93 html.Append(@"""");
94
95 if (String.IsNullOrWhiteSpace(this.Id) == false)
96 {
97 html.Append(@" id=""");
98 html.Append(this.Id);
99 html.Append(@"""");
100 }
101
102 if (String.IsNullOrWhiteSpace(this.Target) == false)
103 {
104 html.Append(@" targetid=""");
105 html.Append(this.Target);
106 html.Append(@"""");
107 }
108 }
109
110 if (!String.IsNullOrWhiteSpace(this.ForeColor) || !String.IsNullOrWhiteSpace(this.BackColor))
111 {
112 html.Append(@" style=""");
113
114 if (String.IsNullOrWhiteSpace(this.ForeColor) == false)
115 {
116 html.Append("color:");
117 html.Append(this.ForeColor);
118 html.Append(";");
119 }
120
121 if (String.IsNullOrWhiteSpace(this.BackColor) == false)
122 {
123 html.Append("background-color:");
124 html.Append(this.BackColor);
125 html.Append(";");
126 }
127
128 html.Append(@"""");
129 }
130
131 html.Append(@">");
132 html.Append(this.Description);
133 html.Append("</");
134 html.Append(tagName);
135 html.Append(">");
136
137 return html.ToString();
138 }
139 }

这就是一个简单的类,从“EnumDescriptionAttribute”派生,而“EnumDescriptionAttribute”也是一个自定义特性类,它将是我们今天所讨论的内容的核心之一,因为我们的代码结构要求,所有用在枚举类型的自定义特性都从“EnumDescriptionAttribute”派生。“EnumDescriptionAttribute”的代码如下:

 1 /// <summary>
2 /// 指定枚举值的说明。
3 /// </summary>
4 [AttributeUsage(AttributeTargets.Enum | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
5 public class EnumDescriptionAttribute : DescriptionAttribute
6 {
7 /// <summary>
8 /// 初始化 <see cref="EnumDescriptionAttribute"/> 类的新实例并带有说明。
9 /// </summary>
10 /// <param name="description">说明文本。</param>
11 public EnumDescriptionAttribute(string description) { this.SetDescription(description); }
12
13 /// <summary>
14 /// 获取或设置用户自定义内容。
15 /// </summary>
16 public object[] UserOptions { get; set; }
17
18 /// <summary>
19 /// 设置说明文本为指定值。
20 /// </summary>
21 /// <param name="description">说明文本。</param>
22 public void SetDescription(string description)
23 {
24 if (description == null) throw new ArgumentNullException("description");
25 if (description.Length == 0) throw new ArgumentOutOfRangeException("description");
26
27 this.DescriptionValue = description;
28 }
29 }

该类是从“System.ComponentModel.DescriptionAttribute”派生而来的,这里我们不需要关心它到底是干嘛的。现在我们了解了自定义特性类,那么到底如何使用它呢?答案是:“反射”!是的,你没有看错,就是反射!目前.NET并没有内置的更好的使用Attribute的途径,唯有反射。

似乎,提到反射,某些朋友就会犯嘀咕“这玩意儿不是会降低性能么?”。是的,反射是可以降低性能,甚至可能出现不可预料的事情,但我们今天已经无法拒绝反射了!关于Attribute的使用方法我这里就不再细细研究了,具体的请参考http://msdn.microsoft.com/zh-cn/library/system.attribute.aspx。在这里我给出实现本文功能所需要的代码片段:

 1 private TEnumDescription _GetDescription(string value)
2 {
3 var fieldName = value;
4 // 解析枚举值字段
5 var fieldInfo = this.EnumType.GetField(fieldName);
6 // 从中获取Attribute信息
7 var attributes = (TEnumDescription[]) fieldInfo.GetCustomAttributes(typeof (TEnumDescription), false);
8
9 return (attributes.Length <= 0) ? (TEnumDescription) (Activator.CreateInstance(typeof (TEnumDescription), new object[] {fieldName})) : attributes[0];
10 }

本文涉及到的代码,其架构用到了很多的泛型思想,因此不熟悉泛型设计的朋友,要多多补习咯!

由于时间关系,今天无法跟大家细细分享EnumVictor<TEnum, TEnumDescription>类的设计理念和中间遇到的问题了,如果大家有疑问可以在文后发表评论提出。在开发这个类的时候,还遇到了位域拆解、多语言、第三方已定义的枚举值等细节问题,这部分功能早已完美解决。如果需要的话,以后我再和大家详细讨论如何处理这些问题。

本文所有代码下载:csharp-enum-EnumVictor.zip

 

posted @ 2012-03-22 12:05  O.C  阅读(2469)  评论(1编辑  收藏  举报