在软件开发过程中,我们经常会为特定的场景下的特定数据定义逻辑意义。比如在用户表中,我们可能会有一个用户状态字段,该字段为整形。如果该字段的值为1则代表用户状态正常,2则代表用户被锁定等等。这些规则应该被写入开发文档里,但是每次都去查文档,也是一件痛苦的事情。其实,在C#中有一个很简单的方法可以实现数据和表象意义之间的转换。枚举既是为此而生。
例如,我们有一个用户状态的枚举,它看起来像这个样子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
/// <summary>
/// 用户状态
/// </summary>
public enum UserState
{
/// <summary>
/// 未激活
/// </summary>
[Description("未激活")]
Nonactivated = 0,
/// <summary>
/// 正常
/// </summary>
[Description("正常")]
Normal = 1,
/// <summary>
/// 锁定
/// </summary>
[Description("锁定")]
Locked = 2
}
|
枚举名称用于区分类型,枚举值用于程序判断,Description特性专为显示而生。多么美好的配合。
1
2
3
4
5
6
7
8
9
|
public static String GetDescription(Enum value)
{
Type type = value.GetType();
FieldInfo item = type.GetField(value.ToString(), BindingFlags.Public | BindingFlags.Static);
if (item == null) return null;
var attribute = Attribute.GetCustomAttribute(item, typeof(DescriptionAttribute)) as DescriptionAttribute;
if (attribute != null && !String.IsNullOrEmpty(attribute.Description)) return attribute.Description;
return null;
}
|
枚举除了用于定义和区分状态之外,也可以参与运算。基于枚举的位操作常常用于权限管理中。多个权限操作可以存储在同一个字段中,而不用在数据表中增加N多列,想想就觉得美好。一个常见的操作权限枚举定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
/// <summary>操作权限</summary>
[Flags]
[Description("操作权限")]
public enum PermissionFlags
{
/// <summary>无权限</summary>
[Description("无")]
None = 0,
/// <summary>所有权限</summary>
[Description("所有")]
All = 1,
/// <summary>添加权限</summary>
[Description("添加")]
Insert = 2,
/// <summary>修改权限</summary>
[Description("修改")]
Update = 4,
/// <summary>删除权限</summary>
[Description("删除")]
Delete = 8,
/// <summary>自定义1权限</summary>
/// <remarks>这里没有接着排16,为了保留给上面使用</remarks>
[Description("自定义1")]
Custom1 = 0x20,
/// <summary>自定义2权限</summary>
[Description("自定义2")]
Custom2 = Custom1 * 2,
/// <summary>自定义3权限</summary>
[Description("自定义3")]
Custom3 = Custom2 * 2,
/// <summary>自定义4权限</summary>
[Description("自定义4")]
Custom4 = Custom3 * 2,
/// <summary>自定义5权限</summary>
[Description("自定义5")]
Custom5 = Custom4 * 2,
/// <summary>自定义6权限</summary>
[Description("自定义6")]
Custom6 = Custom5 * 2,
/// <summary>自定义7权限</summary>
[Description("自定义7")]
Custom7 = Custom6 * 2,
/// <summary>自定义8权限</summary>
[Description("自定义8")]
Custom8 = Custom7 * 2
}
|
如果我们要为用户设定添加和删除权限,只需要为用户的操作权限值设定为10即可(添加权限值为2,删除权限值为8,加起来值为10)。要验证用户是否包含某权限,只需要将该权限与用户拥有的权限值做位运算即可。
1
2
3
4
5
6
7
8
9
10
|
var code = 10;
var flag = (PermissionFlags)code;
if ((PermissionFlags.Insert & flag) == flag)
{
Console.WriteLine("存在添加权限");
}
else
{
Console.WriteLine("没有添加权限");
}
|
在程序圈里摸爬滚打这几年,也勉为其难的步入“三流程序员”的行列。封装一下吧,要对得起自己学过的面向对象。(三流指:封装、继承、多态。)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
[EditorBrowsable(EditorBrowsableState.Never)]
public static class EnumHelper
{
public static T Set<T>(this Enum source, T flag, Boolean value)
{
if (!(source is T)) throw new ArgumentException("枚举标识判断必须是相同的类型!", "source");
ulong s = Convert.ToUInt64(source);
ulong f = Convert.ToUInt64(flag);
if (value)
{
// 必须先检查是否包含这个标识位,因为异或的操作仅仅是取反
if ((s & f) != f) s ^= f;
}
else
s = s | f;
return (T)Enum.ToObject(typeof(T), s);
}
public static Boolean Has(this Enum value, Enum flag)
{
if (value.GetType() != flag.GetType()) throw new ArgumentException("枚举标识判断必须是相同的类型!", "flag");
ulong num = Convert.ToUInt64(flag);
return (Convert.ToUInt64(value) & num) == num;
}
public static String GetDescription(this Enum value)
{
Type type = value.GetType();
FieldInfo item = type.GetField(value.ToString(), BindingFlags.Public | BindingFlags.Static);
if (item == null) return null;
var attribute = Attribute.GetCustomAttribute(item, typeof(DescriptionAttribute)) as DescriptionAttribute;
if (attribute != null && !String.IsNullOrEmpty(attribute.Description)) return attribute.Description;
return null;
}
public static Dictionary<T, String> GetDescriptions<T>() where T : struct
{
return GetDescriptions<T>(typeof(T));
}
public static Dictionary<T, String> GetDescriptions<T>(Type type)
{
var dic = new Dictionary<T, String>();
foreach (FieldInfo item in type.GetFields(BindingFlags.Public | BindingFlags.Static))
{
if (!item.IsStatic) continue;
var value = (T)item.GetValue(null);
string des = item.Name;
var dna = Attribute.GetCustomAttribute(item, typeof(DisplayNameAttribute)) as DisplayNameAttribute;
if (dna != null && !String.IsNullOrEmpty(dna.DisplayName)) des = dna.DisplayName;
var att = Attribute.GetCustomAttribute(item, typeof(DescriptionAttribute)) as DescriptionAttribute;
if (att != null && !String.IsNullOrEmpty(att.Description)) des = att.Description;
dic.Add(value, des);
}
return dic;
}
}
|
但是,做到这里还不够,我们还需要更多的东西来支持界面显示。用于应付在Web开发中常用数据展示和筛选需求。当然,WinForm也可以,只不过需要看官自己去实现。
为WebForm扩展,用于在Repeater控件中展示:
1
2
3
4
5
|
public static String GetDescription<T>(this Page page, Object value)
{
var t = (T)(Convert.ToInt32(value) as Object);
return (t as Enum).GetDescription();
}
|
调用示例:(前提是你得在Web.config中引入相应的命名空间)
1
|
<td><%#this.GetDescription<UserState>( Eval("STATE")) %></td>
|
为DropDownList扩展绑定:
1
2
3
4
5
6
7
8
9
10
|
public static void BindItem<T>(this DropDownList ddl, Boolean allowEmpty = false) where T : struct
{
var dic = Toolkit.Extension.EnumHelper.GetDescriptions<T>();
ddl.Items.Clear();
if (allowEmpty) ddl.Items.Add(new ListItem("==请选择==", String.Empty));
foreach (var item in dic)
{
ddl.Items.Add(new ListItem(item.Value, Convert.ToInt32(item.Key).ToString("D")));
}
}
|
调用示例:
1
|
this.ddlState.BindItem<Common.Define.UserState>();
|
这些就是我工作以来在项目中使用枚举所带来的经验。这种做法大大的提高了编程的效率,可以让程序员更关注业务实现,而不必再为数据为0到底是什么意思扯皮。文中代码来自于真实项目,在你没有用错的情况下可以保证可用性。代码在很大程度上参考了X组件,再次对@大石头表示感谢。如果你感兴趣,可以来新生命团队做客。