C#枚举高级战术
Posted 极客精神
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C#枚举高级战术相关的知识,希望对你有一定的参考价值。
文章开头先给大家出一道面试题:
在设计某小型项目的数据库(假设用的是 mysql)时,如果给用户表(User)添加一个字段(Roles)用来存储用户的角色,你会给这个字段设置什么类型?提示:要考虑到角色在后端开发时需要用枚举表示,且一个用户可能会拥有多个角色。
1|2|3
或
1,2,3
来表示用户拥有多个角色。当然如果角色数量可能超过个位数,考虑到数据库的查询方便(比如用 INSTR 或 POSITION 来判断用户是否包含某个角色),角色的值至少要从数字 10 开始。方案是可行的,可是不是太简单了,有没有更好的方案?
枚举基础
public enum Days
{
Sunday, Monday, Tuesday, // ...
}
public enum Days
{
Sunday = 0, Monday = 1, Tuesday = 2, // ...
}
public enum Days : byte
{
Monday = 1,
Tuesday = 2,
Wednesday = 3,
Thursday = 4,
Friday = 5,
Saturday = 6,
Sunday = 7
}
// 枚举转字符串
string foo = Days.Saturday.ToString(); // "Saturday"
string foo = Enum.GetName(typeof(DayOfWeek), 6); // "Saturday"
// 字符串转枚举
Enum.TryParse("Tuesday", out Days bar); // true, bar = Days.Tuesday
(Days)Enum.Parse(typeof(Days), "Tuesday"); // Days.Tuesday
// 枚举转数字
byte foo = (byte)Days.Monday; // 1
// 数字转枚举
Days foo = (Days)2; // Days.Tuesday
// 获取枚举所属的数字类型
Type foo = Enum.GetUnderlyingType(typeof(Days))); // System.Byte
// 获取所有的枚举成员
Array foo = Enum.GetValues(typeof(MyEnum);
// 获取所有枚举成员的字段名
string[] foo = Enum.GetNames(typeof(Days));
Days d = (Days)21; // 不会报错
Enum.IsDefined(typeof(Days), d); // false
var z = default(Days); // 0
public class ApiStatus
{
[Description("成功")]
OK = 0,
[Description("资源未找到")]
NotFound = 2,
[Description("拒绝访问")]
AccessDenied = 3
}
static class EnumExtensions
{
public static string GetDescription(this Enum val)
{
var field = val.GetType().GetField(val.ToString());
var customAttribute = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute));
if (customAttribute == null) { return val.ToString(); }
else { return ((DescriptionAttribute)customAttribute).Description; }
}
}
static void Main(string[] args)
{
Console.WriteLine(ApiStatus.Ok.GetDescription()); // "成功"
}
用户角色存储问题
public enum Roles
{
Admin = 1,
Member = 2
}
SELECT * FROM `User` WHERE `Roles` & 1 = 1;
SELECT * FROM `User` WHERE `Roles` & 3 = 3;
public class User
{
public int Id { get; set; }
public Roles Roles { get; set; }
}
connection.Query<User>(
"SELECT * FROM `User` WHERE `Roles` & @roles = @roles;",
new { roles = Roles.Admin | Roles.Member });
// 方式一
if (user.Roles & Roles.Admin == Roles.Admin)
{
// 做管理员可以做的事情
}
// 方式二
if (user.Roles.HasFlag(Roles.Admin))
{
// 做管理员可以做的事情
}
var foo = Roles.Admin | Roles.Member;
var bar = foo & ~foo;
枚举的 Flags 特性
public IEnumerable<User> GetUsersInRoles(Roles roles)
{
_logger.LogDebug(roles.ToString());
_connection.Query<User>(
"SELECT * FROM `User` WHERE `Roles` & @roles = @roles;",
new { roles });
}
// 调用
_repository.GetUsersInRoles(Roles.Admin | Roles.Member);
Roles.Admin | Roles.Member
的值是 3,由于 Roles 枚举类型中并没有定义一个值为 3 的字段,所以在方法内 roles 参数显示的是 3。3 这个信息对于我们调试或打印日志很不友好。在方法内,我们并不知道这个 3 代表的是什么。为了解决这个问题,C# 枚举有个很有用的特性:FlagsAtrribute。
[Flags]
public enum Roles
{
Admin = 1,
Member = 2
}
GetUsersInRoles(Roles roles)
方法时,roles 参数的值就会显示为
Admin|Member
了。简单来说,加不加 Flags 的区别是:
var roles = Roles.Admin | Roles.Member;
Console.WriteLing(roles.ToString()); // "3",没有 Flags 特性
Console.WriteLing(roles.ToString()); // "Admin, Member",有 Flags 特性
解决枚举值冲突:2 的幂
[Flags]
public enum Roles
{
Admin = 1,
Member = 2,
Manager = 3
}
1: 00000001
2: 00000010
4: 00000100
8: 00001000
[Flags]
public enum Roles
{
Admin = 1,
Member = 2,
Manager = 4,
Operator = 8
}
[Flags]
public enum Roles
{
Admin = 1 << 0,
Member = 1 << 1,
Manager = 1 << 2,
Operator = 1 << 3
}
总结
本文通过一道小小的面试题引发一连串对枚举的思考。在小型系统中,把用户角色直接存储在用户表是很常见的做法,此时把角色字段设为整型(比如 int)是比较好的设计方案。但与此同时,也要考虑到一些最佳实践,比如使用 Flags 特性来帮助更好的调试和日志输出。也要考虑到实际开发中的各种潜在问题,比如多个枚举值进行或(‘|’)运算与成员值发生冲突的问题。
以上是关于C#枚举高级战术的主要内容,如果未能解决你的问题,请参考以下文章