在一些应用中,通常会用到很多由一些常量来进行描述的状态数据,比如性别(男、女),审核(未审核、已审核)等。在数据库中一般用数字形式来存储,比如0、1等。
不好的做法
经常看到一些应用(ps:最近又看到)没有把这类数据整理成一个数据字典,比如经常重复这样的html:
<select> <option value="0">未审核</option> <option value="1">已审核</option> </select>
然后在后端逻辑判断的时候又用这样的代码:
if (xx == "0") { //... } else if (xx == "1") { //... }
在显示数据的时候又出现这样的代码:
switch(xx) { case "0": return "未审核"; case "1": return "已审核"; }
这样的代码不仅不利于维护,而且,可读性也是非常的差。
使用enum改造
对于以上这类比较固定而且数据量又比较小的状态,可以选择使用enum来改造它,首先定义这么一个枚举类型:
enum AuditState { //未审核 UnAudit = 0, //已审核 Audited }
代表了审核的2种状态,这样在进行判断的时候就可以比较直观的看出来了。但是对于数据绑定,这样做还不够,所以我选择使用Attribute来描述:
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)] public class EnumDescriptionAttribute : Attribute { public EnumDescriptionAttribute(string description) { this.Description = description; } public string Description { get; set; } }
现在,关于审核状态的枚举类型就变成这样了:
enum AuditState { //未审核 [EnumDescription("未审核")] UnAudit = 0, //已审核 [EnumDescription("已审核")] Audited }
然后可以通过取出它的值以及EnumDescription,来实现绑定,无论在可读性还是维护上都是方便许多。
使用缓存
由于获取EnumDescriptionAttribute值是需要通过反射来实现的,如果每次都反射调用,那么性能将会比较糟糕。另外,调用Enum.GetNames、Enum.GetValues以及具体的如AuditState.UnAudit.ToString()方法也都是需要反射的,所以也要注意。
在这里使用静态变量来缓存这些信息,并且利用了泛型的特性,保证每种枚举类型都有一个对应的缓存信息,代码如下:
//T为枚举类型,V为枚举值的类型 class EnumHelper<T, V> where T : struct where V : struct { private static IDictionary<T, string> enumAndDescriptionCache;//描述信息缓存 private static IDictionary<V, string> valueAndDescriptionCache; static EnumHelper() { Initialize(); } /// <summary> /// 初始化 /// </summary> private static void Initialize() { Type type = typeof(T); if (!type.IsEnum) throw new ArgumentException("Generic type must be an enumeration"); string[] enumNames = Enum.GetNames(type); V[] enumValues = Enum.GetValues(type) as V[]; T[] enums = Enum.GetValues(type) as T[]; int l = enumNames.Length; enumAndDescriptionCache = new Dictionary<T, string>(l); valueAndDescriptionCache = new Dictionary<V, string>(l); EnumDescriptionAttribute tempAttr; string temp; for (int i = 0; i < l; i++) { tempAttr = GetDescriptionAttr(type.GetField(enumNames[i])); temp = tempAttr == null ? string.Empty : tempAttr.Description; enumAndDescriptionCache.Add(enums[i], temp); valueAndDescriptionCache.Add(enumValues[i], temp); } } /// <summary> /// 获取枚举类型的描述信息,并加入到缓存中 /// </summary> /// <param name="f"></param> /// <param name="value"></param> private static EnumDescriptionAttribute GetDescriptionAttr(FieldInfo f) { EnumDescriptionAttribute[] attrs = f.GetCustomAttributes(typeof(EnumDescriptionAttribute), false) as EnumDescriptionAttribute[]; if (attrs != null && attrs.Length > 0) { return attrs[0]; } return null; } /// <summary> /// 获取枚举类型的描述 /// </summary> /// <param name="value"></param> /// <returns></returns> public static string GetDescription(T value) { string description = null; if (enumAndDescriptionCache.ContainsKey(value)) enumAndDescriptionCache.TryGetValue(value, out description); return description; } /// <summary> /// 获取枚举类型的描述 /// </summary> /// <param name="value"></param> /// <returns></returns> public static string GetDescriptionByValue(V value) { string description = null; if (valueAndDescriptionCache.ContainsKey(value)) valueAndDescriptionCache.TryGetValue(value, out description); return description; } /// <summary> /// 获取枚举类型所有值及描述 /// </summary> public static IEnumerable<KeyValuePair<T, string>> EnumDescriptions { get { foreach (KeyValuePair<T, string> temp in enumAndDescriptionCache) { yield return temp; } } } /// <summary> /// 获取枚举类型所有值及描述 /// </summary> public static IEnumerable<KeyValuePair<V, string>> ValueDescriptions { get { foreach (KeyValuePair<V, string> temp in valueAndDescriptionCache) { yield return temp; } } } }
具体使用
对于数据绑定可以这样子做:
<select> <asp:Repeater runat="server" id="xx"> <ItemTemplate> <option value="<%# Eval("Key")%>"><%# Eval("Value")%></option> </ItemTemplate> </asp:Repeater> </select>
后端代码:
x.DataSource = EnumHelper<AuditState, int>.ValueDescriptions; x.DataBind();
对以上这种数据绑定可以专门封装成一个用户控件,以便重复利用。
显示数据的时候可以这样:
<asp:Repeater runat="server" id="xx"> <ItemTemplate> <%#EnumHelper<AuditState, int>.GetDescription(Convert.ToInt32(Eval("fromdb..")))%> </ItemTemplate> </asp:Repeater>
判断的时候也可以很直观:
if (xxxx == AuditState.Audited) //...
当然,使用的这些存在类型转换的地方还有待优化,比如到int32类型的转换、到AuditState类型的转换,都可以通过对EnumHelper类进行修改来完成,这里只是为了演示,就不再具体了。
更多需求
以上的方法只能适用于一种语言中,假如还需要显示英文的描述,那么就需要对以上的类型进行调整了。比如可以为EnumDescriptionAttribute添加一个属性:
public string Description { get; set; } public string EnDescription { get; set; }
或者是再创建一个名为EnEnumDescriptionAttribute的类型,无论是哪种方法,都需要在EnumHelper类里面做更多的调整了。
个人认为更好的做法是使用外部xml文件,EnumHelper根据需要加载相应语言的描述文件,如:
public class EnumDescriptionFileAttribute : Attribute { public EnumDescriptionFileAttribute(string lang, string filepath) { this.Lang = lang; this.Filepath = filepath; } public string Lang { get; set; } public string Filepath { get; set; } } [EnumDescriptionFile("AuditState.xml")] enum AuditState { UnAudit = 0, Audited } class EnumHelper...{ void LoadDescriptionFile(lang...) { //... } string GetDescription(lang...) { //... } } <lang value="zh-cn"> <field name="UnAudit" value="未审核" /> <field name="Audited" value="已审核" /> </lang> <lang value="en"> <field name="UnAudit" value="UnAudit" /> <field name="Audited" value="Audited" /> </lang>
这个目前还只是想法,也许有空会去实现它。
或许这么做看起来会有些繁锁,但为了灵活性考虑,这样子去做还是值得的,另外也是看个人喜好了吧。