开源 - Ideal库 - 枚举扩展设计思路及实现难点(三)

news/2024/12/25 14:20:33/文章来源:https://www.cnblogs.com/hugogoos/p/18553991

今天想和大家分享关于枚举扩展设计思路和在实现过程中遇到的难点。

01、设计思路

设计思路说起来其实也很简单,就是通过枚举相关信息:枚举值、枚举名、枚举描述、枚举项、枚举类型,进行各种转换,通过一个信息获取其他信息。比如通过枚举项获取枚举描述、通过枚举类型获取枚举名称-枚举描述键值对用于下拉列表等等。

主要包括以下几类转换实现:

(1)通过枚举值转成:枚举、枚举名称、举描述;

(2)通过枚举名称转成:枚举、枚举值、枚举描述;

(3)通过枚举描述转成:枚举、枚举值、枚举名称;

(4)通过枚举项转成:枚举值、枚举描述;

(5)通过枚举类型转成:枚举值-枚举名称、枚举值-枚举描述、枚举名称-枚举值、枚举名称-枚举描述、枚举描述-枚举值、枚举描述-枚举名称等键值对集合;

02、实现难点

在实现过程中的确遇到一些难点以及需要注意的小细节。

1、枚举名称转枚举中的小细节

在实现枚举名称转枚举的过程中遇到了一个小坑。

枚举本身提供了Enum.TryParse方法用于把枚举名称字符串或者枚举值字符串转为枚举,因此我们选用了此方法实现枚举名称转枚举。但是我们自己这个接口设计目标是把枚举名称转为枚举,因此需要排除误传枚举值字符串的情况。

方案:当转换成功后,我们还需要调用Enum.IsDefined方法检查枚举名称字符串是否是有效的枚举名称字符串,如果不是枚举中现有的有效枚举项,还需要考虑是否为位标志组合情况。

代码如下:

//根据枚举名称转换成枚举,转换失败则返回空
public static TEnum? ToEnumByName<TEnum>(this string name)where TEnum : struct, Enum
{//转换成功则返回结果,否则返回空if (Enum.TryParse<TEnum>(name, out var result)){//检查是否为有效的枚举名称字符串,if (Enum.IsDefined(typeof(TEnum), name)){//返回枚举return result;}else{//计算是否为有效的位标志组合项var isValidFlags = IsValidFlagsMask<ulong, TEnum>(result.ToEnumValue<ulong>());//如果是有效的位标志组合项则返回枚举,否则返回空return isValidFlags ? result : default(TEnum?);}}//返回空return default;
}

2、枚举描述转枚举中的小细节

在实现枚举描述转枚举的过程中对于带位标志枚举处理需要特别小心。我们知道位标志枚举有个特性是枚举项之间可以通过位操作进行组合得到一个有效的枚举项,而且这个枚举项还不存在于当前定义的枚举中。这就要求我们必须处理好组合情况。

方案:因此可以按照以下步骤进行处理。

(1)优先处理当枚举不带位标志的情况;

(2)再处理带位标志的情况;

位标志组合是通过英文逗号[,]拼接的,因此还要考虑到如果一个描述字符串中带有英文逗号[,],但是本身又不是组合的情况。

(3)先把描述当作不是组合情况,先处理一遍,如果转换成功则结束转换;

(4)如果是组合的情况,则计算出组合的每项枚举值,通过位操作得到其对应的枚举项;

而在组合的情况中还需要考虑,如果有组合的项时无效的情况,则这个枚举描述转换应该标记为无效。

(5)判断组合的每一项都是正确的,否则返回空;

(6)将通过组合计算出来的枚举项转为枚举;

因为通过枚举值转枚举过程中还需要明确枚举值类型才行,因此最后一步还需要根据枚举类型实际的基础类型调用不同的转换方法。

具体代码如下:

//根据枚举描述转换成枚举,转换失败返回空
public static TEnum? ToEnumByDesc<TEnum>(this string description)where TEnum : struct, Enum
{var type = typeof(TEnum);var info = GetEnumTypeInfo(type);//不是位标志枚举的情况处理if (!info.IsFlags){return ToEnumDesc<TEnum>(description);}//是位标志枚举的情况处理//不是组合位的情况,本身可能就包含[,]var tenum = ToEnumDesc<TEnum>(description);if (tenum.HasValue){return tenum;}//如果不包含[,],则直接返回if (!description.Contains(',')){return default;}//是组合位的情况var names = description.Split(',');var values = Enum.GetValues(type);//记录有效枚举描述个数var count = 0;ulong mask = 0L;//变量枚举所有项foreach (var name in names){foreach (Enum value in values){//取枚举项描述与目标描述相比较,相同则返回该枚举项if (value.ToEnumDesc() == name){//有效枚举个数加1count++;//将枚举值转为long类型var valueLong = Convert.ToUInt64(value);// 过滤掉负数或无效的值,规范的位标志枚举应该都为非负数if (valueLong >= 0){//合并枚举值至maskmask |= valueLong;}break;}}}//如果两者不相等,说明描述字符串不是一个有效组合项if (count != names.Length){return default;}var underlyingType = Enum.GetUnderlyingType(type);if (underlyingType == typeof(byte)){return ((byte)mask).ToEnumByValue<TEnum>();}else if (underlyingType == typeof(sbyte)){return ((sbyte)mask).ToEnumByValue<TEnum>();}else if (underlyingType == typeof(short)){return ((short)mask).ToEnumByValue<TEnum>();}else if (underlyingType == typeof(ushort)){return ((ushort)mask).ToEnumByValue<TEnum>();}else if (underlyingType == typeof(int)){return ((int)mask).ToEnumByValue<TEnum>();}else if (underlyingType == typeof(uint)){return ((uint)mask).ToEnumByValue<TEnum>();}else if (underlyingType == typeof(long)){return ((long)mask).ToEnumByValue<TEnum>();}else if (underlyingType == typeof(ulong)){return mask.ToEnumByValue<TEnum>();}return default;
}

3、枚举转枚举值中的小细节

我们知道在定义枚举的时候可以指定枚举值类型为以下八种类型sbyte、byte、short、ushort、int、uint、long、ulong,因此所有涉及到返回枚举值的方法,我们要考虑到支持不同类型的枚举值类型返回。

同时我们也知道相同的方法名和入参,并不能通过不同的返回类型区分出不同的重载方法。

方案:因此我们可以通过泛型的方法支持不同类型的枚举值类型返回。

4、通过枚举值转换的小细节

通过上面我们知道枚举值类型有八种,因此涉及到枚举值转换出其他信息是需要同时兼容这八种类型。

要实现这个功能,可以用上面提到的泛型,如果使用泛型我们可以使用struct类型来限制枚举值泛型TValue,因为struct可以覆盖枚举值的八种类型,但是也引发了另一个问题就是float、double、DateTime都是struct类型,这样就导致这些类型在编辑器中也可以点出ToEnumByValue等相关方法。

作为一个公共封装方法,这种方式显然是对用户不友好的。

因此我们选择另外一种方式:重载方法来实现,通过封装方法多写一些代码来实现对用户友好调用。

5、如何高效返回键值对数据

在通过枚举类型转成各种键值对集合时,有些方法需要用到反射,因此如何高效的获取这些信息就变成重中之重。

最直接的想法是直接把每种枚举类型对应的键值对集合直接缓存起来,下次用到的时候直接从缓存中获取即可。

但是仔细详细,一共有3个数据:枚举值、枚举名称、枚举描述。两两组合有6种情况,也就是要6个缓存存储12个数据,这样其实就相当于浪费了3倍的空间。

当然我们可以把这三个值存一份,然后在需要的是直接组合获取,但是我们仔细分析一些是否有必要把所有数据都缓存下来。

枚举值会涉及到类型转换,枚举名称涉及调用ToString方法,枚举描述需要用到反射。这其中最好性能的非枚举描述不可,因此枚举描述肯定需要缓存,枚举值和枚举名称可以考虑缓存。

假如我们只缓存枚举描述,那么枚举值和枚举名称在使用的时候直接转换,那么我们执行要一份记录枚举描述的缓存以及一份记录枚举类型对应其所有枚举项的缓存即可。另外因为枚举对应的是否带位标志标记以及掩码可以同时记录下来。

代码如下:

public static partial class EnumExtension
{//枚举类型基础信息private sealed class EnumTypeInfo{//是否是位标志public bool IsFlags { get; set; }//枚举掩码public ulong Mask { get; set; }//枚举项集合public List<Enum> Items { get; set; } = [];}//存储枚举项对应的描述private static readonly ConcurrentDictionary<Enum, string> _descs = new();//存储枚举相关信息

获取枚举名称-枚举描述键值对代码如下,首先获取枚举类型基础信息,然后把枚举项集合转为键值对集合。

//获取枚举名称+枚举描述
public static Dictionary<string, string> ToEnumNameDescs(this Type type)
{//根据type获取枚举类型基础信息var info = GetEnumTypeInfo(type);//通过枚举项集合转为目标类型return info.Items.ToDictionary(r => r.ToString(), r => r.ToEnumDesc());
}

6、如何识别一个枚举值是否为有效的位标志组合

如何识别一个枚举值是否是一个有效的位标志组合,可以说是整个代码中最难的部分了,其实前面也有提到,主要应用掩码的思想,上一章节已经详解讲解了这里就不在赘述了。

稍晚些时候我会把库上传至Nuget,大家可以直接使用Ideal.Core.Common。

:测试方法代码以及示例源码都已经上传至代码库,有兴趣的可以看看。https://gitee.com/hugogoos/Ideal

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/836447.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

安装新版pycharm 专业版 百分百成功

安装新版pycharm 专业版 百分百成功 创建时间:20241103 1. 下载插件 1.1 打开下面的网站 https://3.jetbra.in/1.2 选择能进去的网站 没使用鸡哥上网使用鸡哥上网1.3 进入的界面1.4 下载插件 查看这里支持的版本号2. 在官网下载对应的版本 2.1 官网的历史版本位置 我这里使用…

vercel无法导入github项目

背景 点击import后,浏览器打开了github的项目链接,而不是有关于项目部署的设置。根据网络上的说法,import后就会出现相关的配置页面,然而没有出现下面是预想的页面然而并没有出现。 解决 使用cli工具。 npm install -g vercel# 登录 vercel login # 部署 vercel回到官网,发…

福气满满-冲刺日志(第一天)

这个作业属于哪个课程 https://edu.cnblogs.com/campus/fzu/SE2024作业要求 https://edu.cnblogs.com/campus/fzu/SE2024/homework/13305团队名称 福气满满团队成员学号-名字 052203132童潇剑,102201226陈潇健,102201235曾炜坤,102201234洪庆杨,102201224 陈博涵,18220031…

11.8 每日总结(继续学习redis)

今天依旧Redis,学习时长2小时。数据结构:

[极客大挑战 2019]Secret File

首页什么都没有,查看网页源码,发现有一个链接,提示found除去view-source访问,得到以下页面,点击secret直接查阅结束,没回显flag,依旧在卖关子。使用burp suite抓包获取返回的源码,找到注释中有个文件secr3t.php访问secr3t.php得到代码里说flag在flag.php里,直接访问fl…

CCTV 新闻节目 All In One

CCTV 新闻节目 All In One 世界周刊 《世界周刊》着力于信息整合,打破不同媒体间隔,从海量的信息碎片中捕捉世界发展的轨迹,从每周的国际事件中提炼出最有价值的内容。CCTV 新闻节目 All In One世界周刊《世界周刊》着力于信息整合,打破不同媒体间隔,从海量的信息碎片中捕…

厉害了,8.7w人打满分!如果我找 BUG 能像他一样厉害就好了!

你好呀,我是歪歪。 最近在 B 站上看到一个让我觉得“非常哇塞”视频的:https://www.bilibili.com/video/BV1y4421U72G/视频的播放量有接近 400w,在视频快结尾的地方,UP 主设置了一个打分的互动:8.7 万人参与,平均 10 分。这参与度和平均分,歪师傅作为在 B 站混迹多年的用…

JAVA反序列化学习-CommonsCollections2(基于ysoserial)

环境准备 JDK1.8(8u421)这里ysoserial,我以本地的JDK8版本为准、commons-collections4(4.0 以ysoserial给的版本为准)、javassist(3.12.1.GA) cc4.0、ClassPool <dependency><groupId>org.apache.commons</groupId><artifactId>commons-collections4&…

先天软工圣体队———冲刺日记(第四天)

1. 每位团队成员的分工与进度成员 完成的任务 完成的任务时长 剩余时间何愉心、邱雨涵 对vlog做最终的修改,美化。为实验课做准备 2小时 6小时陈思雨,郑奇键 搜集筛选资料,填充资料库 1小时 11小时陈尚冰 对web页面的边框,按键美化 3小时 10小时柯鸿毅 AI接口调用测试,修改…

电子元器件外贸购买

前言 最近忙着采购外国一款品牌的连接器(说人话就是端子),有很多专业术语需要理解。PKG是什么意思? 指产品的包装方式或包装材料, PKG是packaging的缩写. 请结合具体的环境确认是指包装方式还是包装材料.有时, 客户会说quotation package, 这里指的是将所有报价资料打包,里…

看过这个,你可能更了解指针4

如图,我们先看以下问题在稍微思考过后,先把你的答案记下来吧。 接下来我们分析 ****1中arr被单独放入sizeof中,表示整个数组,则计算整个数组的大小, 且sizeof计算的大小包含\0,则在sizeof看来数组arr有7个元素, 类型为char,7乘1=7 ****2中arr没有单独放入sizeof中,也没…

20222312 2024-2025-1《网络与系统攻防技术》实验五实验报告

一、实验内容 1.1 知识回顾 1.2实验内容 从besti.edu.cn中选择一个DNS域名进行查询,获取信息 尝试获取BBS、论坛、QQ、MSN中某一好友的IP地址,并查询获取该好友所在的具体地理位置。 使用nmap开源软件对靶机环境进行扫描,回答以下问题并给出操作命令。 使用Nessus开源软件对…