当前位置: 代码网 > it编程>编程语言>Asp.net > C#枚举的高级应用

C#枚举的高级应用

2024年05月18日 Asp.net 我要评论
文章开头先给大家出一道面试题:在设计某小型项目的数据库(假设用的是 mysql)时,如果给用户表(user)添加一个字段(roles)用来存储用户的角色,你会给这个字段设置什么类型?提示:要考虑到角色

文章开头先给大家出一道面试题:

在设计某小型项目的数据库(假设用的是 mysql)时,如果给用户表(user)添加一个字段(roles)用来存储用户的角色,你会给这个字段设置什么类型?提示:要考虑到角色在后端开发时需要用枚举表示,且一个用户可能会拥有多个角色。

映入你脑海的第一个答案可能是:varchar 类型,用分隔符的方式来存储多个角色,比如用 1|2|3 或 1,2,3 来表示用户拥有多个角色。当然如果角色数量可能超过个位数,考虑到数据库的查询方便(比如用 instr 或 position 来判断用户是否包含某个角色),角色的值至少要从数字 10 开始。方案是可行的,可是不是太简单了,有没有更好的方案?更好的回答应是整型(int、bigint 等),优点是写 sql 查询条件更方便,性能、空间上都优于 varchar。但整型毕竟只是一个数字,怎么表示多个角色呢?此时想到了二进制位操作的你,心中应该早有了答案。且保留你心中的答案,接着看完本文,或许你会有意外的收获,因为实际应用中可能还会遇到一连串的问题。为了更好的说明后面的问题,我们先来回顾一下枚举的基础知识。

枚举基础

枚举类型的作用是限制其变量只能从有限的选项中取值,这些选项(枚举类型的成员)各自对应于一个数字,数字默认从 0 开始,并以此递增。例如:

public enum days
{
    sunday, monday, tuesday, // ...
}

其中 sunday 的值是 0,monday 是 1,以此类推。为了一眼能看出每个成员代表的值,一般推荐显示地将成员值写出来,不要省略:

public enum days
{
    sunday = 0, monday = 1, tuesday = 2, // ...
}

c# 枚举成员的类型默认是 int 类型,通过继承可以声明枚举成员为其它类型,比如:

public enum days : byte
{
    monday = 1,
    tuesday = 2,
    wednesday = 3,
    thursday = 4,
    friday = 5,
    saturday = 6,
    sunday = 7
}

枚举类型一定是继承自 byte、sbyte、short、ushort、int、uint、long 和 ulong 中的一种,不能是其它类型。下面是几个枚举的常见用法(以上面的 days 枚举为例):

// 枚举转字符串
string foo = days.saturday.tostring(); // "saturday"
string foo = enum.getname(typeof(days), 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

即使枚举没有值为 0 的成员,它的默认值永远都是 0。

var z = default(days); // 0

枚举可以通过 description、display 等特性来为成员添加有用的辅助信息,比如:

public enum 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
}

这样,如果某个用户同时拥有 admin 和 member 两种角色,那么 user 表的 roles 字段就应该存 3。那问题来了,此时若查询所有拥有 admin 角色的用户的 sql 该怎么写呢?对于有基础的程序员来说,这个问题很简单,只要用位操作符逻辑与(‘&’)来查询即可。

select * from `user` where `roles` & 1 = 1;

同理,查询同时拥有这两种角色的用户,sql 语句应该这么写:

select * from `user` where `roles` & 3 = 3;

对这条 sql 语句用 c# 来实现查询是这样的(为了简单,这里使用了 dapper):

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 });

对应的,在 c# 中要判断用户是否拥有某个角色,可以这么判断:

// 方式一
if ((user.roles & roles.admin) == roles.admin)
{
    // 做管理员可以做的事情
}

// 方式二
if (user.roles.hasflag(roles.admin))
{
    // 做管理员可以做的事情
}

同理,在 c# 中你可以对枚举进行任意位逻辑运算,比如要把角色从某个枚举变量中移除:

var foo = roles.admin | roles.member;
var bar = foo & ~roles.admin;

这就解决了文章前面提到的用整型来存储多角色的问题,不论数据库还是 c# 语言,操作上都是可行的,而且也很方便灵活。

枚举的 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
}

加上这个 flags 特性后,我们再来调试 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 特性

给枚举加上 flags 特性,我觉得应当视为 c# 编程的一种最佳实践,在定义枚举时尽量加上 flags 特性。

解决枚举值冲突:2 的幂

到这,枚举类型 roles 一切看上去没什么问题,但如果现在要增加一个角色:mananger,会发生什么情况?按照数字值递增的规则,manager 的值应当设为 3。

[flags]
public enum roles
{
    admin = 1,
    member = 2,
    manager = 3
}

能不能把 manager 的值设为 3?显然不能,因为 admin 和 member 进行位的或逻辑运算(即:admin | member) 的值也是 3,表示同时拥有这两种角色,这和 manager 冲突了。那怎样设值才能避免冲突呢?既然是二进制逻辑运算“或”会和成员值产生冲突,那就利用逻辑运算或的规律来解决。我们知道“或”运算的逻辑是两边只要出现一个 1 结果就会 1,比如 1|1、1|0 结果都是 1,只有 0|0 的情况结果才是 0。那么我们就要避免任意两个值在相同的位置上出现 1。根据二进制满 2 进 1 的特点,只要保证枚举的各项值都是 2 的幂即可。比如:

1:  00000001
2:  00000010
4:  00000100
8:  00001000

再往后增加的话就是 16、32、64...,其中各值不论怎么相加都不会和成员的任一值冲突。这样问题就解决了,所以我们要这样定义 roles 枚举的值:

[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 特性来帮助更好的调试和日志输出。也要考虑到实际开发中的各种潜在问题,比如多个枚举值进行或(‘|’)运算与成员值发生冲突的问题。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持代码网。

(0)

相关文章:

  • ASP.NET Core中间件

    1.前言整个http request请求跟http response返回结果之间的处理流程是一个请求管道(request pipeline)。而中间件(middleware)则是一…

    2024年05月18日 编程语言
  • ASP.NET MVC中_ViewStart.cshtml作用介绍

    一、引言_viewstart.cshtml是在asp.net mvc 3.0及更高版本以后出现的,用razor模板引擎新建项目后,views目录下面会出现一个这样的文件:打开_vi…

    2024年05月18日 编程语言
  • ASP.NET Core中的静态文件

    1.前言当我们创建core项目的时候,web根目录下会有个wwwroot文件目录,wwwroot文件目录里面默认有html、css、img、javascript等文件,而这些文件都…

    2024年05月18日 编程语言
  • ASP.NET MVC中两个配置文件的作用详解

    前言在新建完一个mvc项目之后,你会发现整个整个项目结构中存在有两个web.config文件,如下图所示:这两个配置文件,一个位于项目的根目录下面,一个位于views文件夹下面,这…

    2024年05月18日 编程语言
  • ASP.NET Core使用IHttpClientFactory发出HTTP请求

    1.httpclient类使用存在的问题httpclient类的使用所存在的问题,百度搜索的文章一大堆,好多都是单纯文字描述,让人感觉不太好理解,为了更好理解httpclient使…

    2024年05月18日 编程语言
  • ASP.NET MVC项目部署方式介绍

    asp.net mvc编写的程序需要部署到iis上面才能进行访问,部署方式分为两种。一、直接用源代码部署第一种方式可以直接使用源代码进行部署。部署步骤:1、新建网站在iis里面选择…

    2024年05月18日 编程语言

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com