开源 - Ideal库 - 常用枚举扩展方法(二)

书接上回,今天继续和大家享一些关于枚举操作相关的常用扩展方法。

在这里插入图片描述

今天主要分享通过枚举值转换成枚举、枚举名称以及枚举描述相关实现。

我们首先修改一下上一篇定义用来测试的正常枚举,新增一个枚举项,代码如下:

//正常枚举
internal enum StatusEnum
{
    [Description("正常")]
    Normal = 0,
    [Description("待机")]
    Standby = 1,
    [Description("离线")]
    Offline = 2,
    Online = 3,
    Fault = 4,
}

01、根据枚举值转换成枚举

该方法接收枚举值作为参数,并转为对应枚举,转换失败则返回空。

枚举类Enum中自带了两种转换方法,其一上篇文章使用过即Parse,这个方法可以接收string或Type类型作为参数,其二为ToObject方法,接收参数为整数类型。

在这里插入图片描述

因为枚举值本身就是整数类型,因此我们选择ToObject方法作为最终实现,这样就避免使用Parse方法时还需要把整数类型参数进行转换。

同时我们通过上图可以发现枚举值可能的类型有uint、ulong、ushort、sbyte、long、int、byte、short八种情况。

因此下面我们以int类型作为示例,进行说明,但同时考虑到后面通用性、扩展性,我们再封装一个公共的泛型实现可以做到支持上面八种类型。因此本方法会调用一个内部私有方法,具体如下:

//根据枚举值转换成枚举,转换失败则返回空
public static T? ToEnumByValue<T>(this int value) where T : struct, Enum
{
    //调用根据枚举值转换成枚举方法
    return ToEnumByValue<int, T>(value);
}

而内部私有方法即通过泛型实现对多种类型支持,我们先看代码实现再详细讲解,具体代码如下:

//根据枚举值转换成枚举,转换失败则返回空
private static TEnum? ToEnumByValue<TSource, TEnum>(this TSource value)
    where TSource : struct
    where TEnum : struct, Enum
{
    //检查整数值是否是有效的枚举值并且是否是有效位标志枚举组合项
    if (!Enum.IsDefined(typeof(TEnum), value) && !IsValidFlagsMask<TSource, TEnum>(value))
    {
        //非法数据则返回空
        return default;
    }
    //有效枚举值则进行转换
    return (TEnum)Enum.ToObject(typeof(TEnum), value);
}

该方法首先验证参数合法性,验证通过直接使用ToObject方法进行转换。

参数验证首先通过Enum.IsDefined方法校验参数是否是有效的枚举项,这是因为无论是ToObject方法还是Parse方法对于整数类型参数都是可以转换成功的,无论这个参数是否是枚举中的项,因此我们需要首先排查掉非枚举中的项。

而该方法中IsValidFlagsMask方法主要是针对位标志枚举组合情况,位标志枚举特性导致即使我们枚举项中没有定义相关项,但是可以通过组合得到而且是合法的,因此我们需要对位标志枚举单独处理,具体实现代码如下:

//存储枚举是否为位标志枚举
private static readonly ConcurrentDictionary<Type, bool> _flags = new();
//存储枚举对应掩码值
private static readonly ConcurrentDictionary<Type, long> _flagsMasks = new();
private static bool IsValidFlagsMask<TSource, TEnum>(TSource source)
    where TSource : struct
    where TEnum : struct, Enum
{
    var type = typeof(TEnum);
    //判断是否为位标志枚举,如果有缓存直接获取,否则计算后存入缓存再返回
    var isFlags = _flags.GetOrAdd(type, (key) =>
    {
        //检查枚举类型是否有Flags特性
        return Attribute.IsDefined(key, typeof(FlagsAttribute));
    });
    //如果不是位标志枚举则返回false
    if (!isFlags)
    {
        return false;
    }
    //获取枚举掩码,如果有缓存直接获取,否则计算后存入缓存再返回
    var mask = _flagsMasks.GetOrAdd(type, (key) =>
    {
        //初始化存储掩码变量
        var mask = 0L;
        //获取枚举所有值
        var values = Enum.GetValues(key);
        //遍历所有枚举值,通过位或运算合并所有枚举值
        foreach (Enum enumValue in values)
        {
            //将枚举值转为long类型
            var valueLong = Convert.ToInt64(enumValue);
            // 过滤掉负数或无效的值,规范的位标志枚举应该都为非负数
            if (valueLong >= 0)
            {
                //合并枚举值至mask
                mask |= valueLong;
            }
        }
        //返回包含所有枚举值的枚举掩码
        return mask;
    });
    var value = Convert.ToInt64(source);
    //使用待验证值value和枚举掩码取反做与运算
    //结果等于0表示value为有效枚举值
    return (value & ~mask) == 0;
}

该方法首先是判断当前枚举是否是位标志枚举即枚举是否带有Flags特性,可以通过Attribute.IsDefined实现,考虑到性能问题,因此我们把枚举是否为位标志枚举存入缓存中,当下次使用时就不必再次判断了。

如果当前枚举不是位标志枚举则之间返回false。

如果是位标志枚举则进入关键点了,如何判断一个值是否为一组值或一组值任意组合里面的一个?

这里用到了位掩码技术,通过按位或对所有枚举项进行标记,达到合并所有枚举项的目的,同时还包括可能的组合情况。

这里存储掩码的变量定义为long类型,因为我们需要兼容上文提到的八种整数类型。同时一个符合规范的位标志枚举设计枚举值是不会出现负数的因此也需要过滤掉。

同时考虑到性能问题,也需要把每个枚举对于的枚举掩码记录到缓存中方便下次使用。

拿到枚举掩码后我们需要对其进行取反,即表示所有符合要求的值,此值再与待验证参数做按位与操作,如果不为0表示待验证才是为无效枚举值,否则为有效枚举值。

关于位操作我们后面找机会再单独详解讲解其中原理和奥秘。

讲解完整个实现过程我们还需要对该方法进行详细的单元测试,具体分为以下几种情况:

(1) 正常枚举值,成功转换成枚举;

(2) 不存在的枚举值,但是可以通过枚举项按位或合并得到,返回空;

(3) 不存在的枚举值,也不可以通过枚举项按位或合并得到,返回空;

(4) 正常位标志枚举值,成功转换成枚举;

(5) 不存在的枚举值,但是可以通过枚举项按位或合并得到,成功转换成枚举;

(6) 不存在的枚举值,也不可以通过枚举项按位或合并得到,返回空;

具体实现代码如下:

[Fact]
public void ToEnumByValue()
{
    //正常枚举值,成功转换成枚举
    var status = 1.ToEnumByValue<StatusEnum>();
    Assert.Equal(StatusEnum.Standby, status);
    //不存在的枚举值,但是可以通过枚举项按位或合并得到,返回空
    var isStatusNull = 5.ToEnumByValue<StatusEnum>();
    Assert.Null(isStatusNull);
    //不存在的枚举值,也不可以通过枚举项按位或合并得到,返回空
    var isStatusNull1 = 8.ToEnumByValue<StatusEnum>();
    Assert.Null(isStatusNull1);
    //正常位标志枚举值,成功转换成枚举
    var flags = 3.ToEnumByValue<TypeFlagsEnum>();
    Assert.Equal(TypeFlagsEnum.HttpAndUdp, flags);
    //不存在的枚举值,但是可以通过枚举项按位或合并得到,成功转换成枚举
    var flagsGroup = 5.ToEnumByValue<TypeFlagsEnum>();
    Assert.Equal(TypeFlagsEnum.Http | TypeFlagsEnum.Tcp, flagsGroup);
    //不存在的枚举值,也不可以通过枚举项按位或合并得到,返回空
    var isFlagsNull = 8.ToEnumByValue<TypeFlagsEnum>();
    Assert.Null(isFlagsNull);
}

02、根据枚举值转换成枚举或默认值

该方法是对上一个方法的补充,用于处理转换不成功时,则返回一个指定默认枚举,具体代码如下:

//根据枚举值转换成枚举,转换失败则返回默认枚举
public static T? ToEnumOrDefaultByValue<T>(this int value, T defaultValue) 
    where T : struct, Enum
{
    //调用根据枚举值转换成枚举方法
    var result = value.ToEnumByValue<T>();
    if (result.HasValue)
    {
        //返回枚举
        return result.Value;
    }
    //转换失败则返回默认枚举
    return defaultValue;
}

然后我们进行一个简单单元测试,代码如下:

[Fact]
public void ToEnumOrDefaultByValue()
{
    //正常枚举值,成功转换成枚举
    var status = 1.ToEnumOrDefaultByValue(StatusEnum.Offline);
    Assert.Equal(StatusEnum.Standby, status);
    //不存在的枚举值,返回指定默认枚举
    var statusDefault = 5.ToEnumOrDefaultByValue(StatusEnum.Offline);
    Assert.Equal(StatusEnum.Offline, statusDefault);
}

03、根据枚举值转换成枚举名称

该方法接收枚举值作为参数,并转为对应枚举名称,转换失败则返回空。

实现则是通过根据枚举值转换成枚举方法获得枚举,然后通过枚举获取枚举名称,具体代码如下:

//根据枚举值转换成枚举名称,转换失败则返回空
public static string? ToEnumNameByValue<T>(this int value) where T : struct, Enum
{
    //调用根据枚举值转换成枚举方法
    var result = value.ToEnumByValue<T>();
    if (result.HasValue)
    {
        //返回枚举名称
        return result.Value.ToString();
    }
    //转换失败则返回空
    return default;
}

我们进行如下单元测试:

[Fact]
public void ToEnumNameByValue()
{
    //正常枚举值,成功转换成枚举名称
    var status = 1.ToEnumNameByValue<StatusEnum>();
    Assert.Equal("Standby", status);
    //不存在的枚举值,返回空
    var isStatusNull = 10.ToEnumNameByValue<StatusEnum>();
    Assert.Null(isStatusNull);
    //正常位标志枚举值,成功转换成枚举名称
    var flags = 3.ToEnumNameByValue<TypeFlagsEnum>();
    Assert.Equal("HttpAndUdp", flags);
    //不存在的位标志枚举值,返回空
    var isFlagsNull = 20.ToEnumNameByValue<TypeFlagsEnum>();
    Assert.Null(isFlagsNull);
}

04、根据枚举值转换成枚举名称默认值

该方法是对上一个方法的补充,用于处理转换不成功时,则返回一个指定默认枚举名称,具体代码如下:

//根据枚举值转换成枚举名称,转换失败则返回默认枚举名称
public static string? ToEnumNameOrDefaultByValue<T>(this int value, string defaultValue) 
    where T : struct, Enum
{
    //调用根据枚举值转换成枚举名称方法
    var result = value.ToEnumNameByValue<T>();
    if (!string.IsNullOrWhiteSpace(result))
    {
        //返回枚举名称
        return result;
    }
    //转换失败则返回默认枚举名称
    return defaultValue;
}

进行简单的单元测试,具体代码如下:

[Fact]
public void ToEnumNameOrDefaultByValue()
{
    //正常枚举值,成功转换成枚举名称
    var status = 1.ToEnumNameOrDefaultByValue<StatusEnum>("离线");
    Assert.Equal("Standby", status);
    //不存在的枚举名值,返回指定默认枚举名称
    var statusDefault = 12.ToEnumNameOrDefaultByValue<StatusEnum>("离线");
    Assert.Equal("离线", statusDefault);
}

05、根据枚举值转换成枚举描述

该方法接收枚举值作为参数,并转为对应枚举名称,转换失败则返回空。

实现则是通过根据枚举值转换成枚举方法获得枚举,然后通过枚举获取枚举描述,具体代码如下:

//根据枚举值转换成枚举描述,转换失败则返回空
public static string? ToEnumDescByValue<T>(this int value) where T : struct, Enum
{
    //调用根据枚举值转换成枚举方法
    var result = value.ToEnumByValue<T>();
    if (result.HasValue)
    {
        //返回枚举描述
        return result.Value.ToEnumDesc();
    }
    //转换失败则返回空
    return default;
}

单元测试如下:

[Fact]
public void ToEnumDescByValue()
{
    //正常位标志枚举值,成功转换成枚举描述
    var flags = 3.ToEnumDescByValue<TypeFlagsEnum>();
    Assert.Equal("Http协议,Udp协议", flags);
    //正常的位标志枚举值,组合项不存在,成功转换成枚举描述
    var flagsGroup1 = 5.ToEnumDescByValue<TypeFlagsEnum>();
    Assert.Equal("Http协议,Tcp协议", flagsGroup1);
}

06、根据枚举值转换成枚举描述默认值

该方法是对上一个方法的补充,用于处理转换不成功时,则返回一个指定默认枚举描述,具体代码如下:

//根据枚举值转换成枚举描述,转换失败则返回默认枚举描述
public static string? ToEnumDescOrDefaultByValue<T>(this int value, string defaultValue) 
    where T : struct, Enum
{
    //调用根据枚举值转换成枚举描述方法
    var result = value.ToEnumDescByValue<T>();
    if (!string.IsNullOrWhiteSpace(result))
    {
        //返回枚举描述
        return result;
    }
    //转换失败则返回默认枚举描述
    return defaultValue;
}

单元测试代码如下:

[Fact]
public void ToEnumDescOrDefaultByValue()
{
    //正常枚举值,成功转换成枚举描述
    var status = 1.ToEnumDescOrDefaultByValue<StatusEnum>("测试");
    Assert.Equal("待机", status);
    //不存在的枚举值,返回指定默认枚举描述
    var statusDefault = 11.ToEnumDescOrDefaultByValue<StatusEnum>("测试");
    Assert.Equal("测试", statusDefault);
}

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何平滑切换Containerd数据目录

如何平滑切换Containerd数据目录 大家好&#xff0c;我是秋意零。 这是工作中遇到的一个问题。搭建的服务平台&#xff0c;在使用的过程中频繁出现镜像本地拉取不到问题&#xff08;在项目群聊中老是被人出来&#x1f605;&#xff09;原因是由于/目录空间不足导致&#xff0…

(附项目源码)Java开发语言,监督管家APP的设计与实现 58,计算机毕设程序开发+文案(LW+PPT)

摘要 随着互联网的快速发展和智能手机的普及&#xff0c;越来越多的用户选择通过移动应用程序进行事项设定、提醒通知和事项打卡。监督管家APP作为一个专注于事项设定、提醒通知、事项打卡的监督管理平台&#xff0c;具有广泛的应用前景和商业价值。本研究旨在构建一个功能丰富…

ffmpeg+D3D实现的MFC音视频播放器,支持录像、截图、音视频播放、码流信息显示等功能

一、简介 本播放器是在vs2019下开发&#xff0c;通过ffmpeg实现拉流解码功能&#xff0c;通过D3D实现视频的渲染功能。截图功能采用libjpeg实现&#xff0c;可以截取jpg图片&#xff0c;图片的默认保存路径是在C:\MYRecPath中。录像功能采用封装好的类Mp4Record实现&#xff0c…

LLM在Transformer上的改动

LLM在Transformer上的改动 1.multi-head共享1.1BERT的逻辑1.2multi-head共享 2.attention的前后网络2.1传统Transformer&#xff1a;2.2GPTJ结构&#xff1a; 3.归一化层的位置&#xff08;LayerNorm&#xff09;4.归一化层函数的选择4.1LayerNorm4.2RMSNorm 3.激活函数4.LLama…

git命令及原理

git: 目录则被称之为“树” 文件被称作 Blob 对象. git help <command>: 获取 git 命令的帮助信息 git init: 创建一个新的 git 仓库&#xff0c;其数据会存放在一个名为 .git 的目录下 git status: 显示当前的仓库状态 git add <filename>: 添加文件到暂存区 git …

scala 迭代更新

在Scala中&#xff0c;迭代器&#xff08;Iterator&#xff09;是一种用于遍历集合&#xff08;如数组、列表、集合等&#xff09;的元素而不暴露其底层表示的对象。迭代器提供了一种统一的方法来访问集合中的元素&#xff0c;而无需关心集合的具体实现。 在Scala中&#xff0c…

快速掌握——python类 封装[私有属性方法]、继承【python进阶】(内附代码)

1.类的定义 与 实例化对象 在python中使用class关键字创建一个类。 举例子 class Stu(object):id 1001name 张三def __init__(self):passdef fun1(self):pass# 实例化对象 s1 Stu() s2 Stu() print(s1.name) print(s2.name) 第一个方法 __init__是一种特殊的方法&#x…

51c自动驾驶~合集10

我自己的原文哦~ https://blog.51cto.com/whaosoft/11638131 #端到端任务 说起端到端&#xff0c;每个从业者可能都觉得会是下一代自动驾驶量产方案绕不开的点&#xff01;特斯拉率先吹响了方案更新的号角&#xff0c;无论是完全端到端&#xff0c;还是专注于planner的模型&a…

BFS 算法专题(三):BFS 解决边权为 1 的最短路问题

目录 1. 迷宫中离入口最近的出口 1.1 算法原理 1.2 算法代码 2. 最小基因变化 ★★★ 2.1 算法原理 2.2 算法代码 3. 单词接龙 3.1 算法原理 3.2 算法代码 4. 为高尔夫比赛砍树 (hard) 4.1 算法原理 4.2 算法代码 1. 迷宫中离入口最近的出口 . - 力扣&#xff08;…

Flink_DataStreamAPI_执行环境

DataStreamAPI_执行环境 1创建执行环境1.1getExecutionEnvironment1.2createLocalEnvironment1.3createRemoteEnvironment 2执行模式&#xff08;Execution Mode&#xff09;3触发程序执行 Flink程序可以在各种上下文环境中运行&#xff1a;我们可以在本地JVM中执行程序&#x…

46.第二阶段x86游戏实战2-拆解自动打怪流程

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 本人写的内容纯属胡编乱造&#xff0c;全都是合成造假&#xff0c;仅仅只是为了娱乐&#xff0c;请不要…

解决C盘空间不足的三种方案

方案一&#xff1a;网上盛传的C盘磁盘碎片整理&#x1f9e9;&#xff08;原理&#xff1a;将分散的文件片段整理到相邻的磁盘区域&#xff0c;减少文件的碎片化程度&#xff09;(效果不明显) 方案二&#xff1a;把其他盘的空间给C盘 &#x1f4bd;&#xff08;效果显著&#xf…

同一套SDK 兼容第二块板卡

尽可能分开写,避免兼容性变差

计算机网络高频八股文面试题及参考答案

请简述 TCP 和 UDP 的区别&#xff1f; TCP&#xff08;传输控制协议&#xff09;和 UDP&#xff08;用户数据报协议&#xff09;是两种不同的传输层协议&#xff0c;它们有以下区别。 从连接方式上看&#xff0c;TCP 是面向连接的协议。在通信之前&#xff0c;需要通过三次握手…

前缀和算法习题篇(上)

1.一维前缀和 题目描述&#xff1a; 解法一&#xff1a;暴力解法&#xff1a;模拟 时间复杂度是O(n*q),会超时。 解法二&#xff1a;前缀和解法&#xff1a;快速求出数组中某一个连续区间的和 快速是指O(1),前缀和思想可把时间复杂度可降到O(q)。 算法思路&#xff1a; 先预处…

uniapp路由与页面跳转详解:API调用与Navigator组件实战

UniApp路由与页面跳转详解&#xff1a;API调用与Navigator组件实战 路由 uniapp页面路由为框架统一管理&#xff0c;开发者需要在page.json里面配置每个路由页面的路径及页面样式。 路由跳转 uniapp有两种页面路由跳转方式&#xff0c;调用API跳转和navigator组件跳转。 调…

linux-DNS解析

dns解析 dns&#xff1a;域名系统&#xff0c;将域名和ip地址互相映射的一个分布式的数据库&#xff0c;方便用户访问互联网。 ip地址&#xff1a;是所有设备和网站在互联网上的唯一地址&#xff0c;通信一定是ip和ip之间的通信。 dns解析&#xff1a;根据域名在互联网当中找…

Playwright 快速入门:Playwright 是一个用于浏览器自动化测试的 Node.js 库

Playwright 是一个用于浏览器自动化测试的 Node.js 库&#xff0c;它支持 Chromium, Firefox 和 WebKit 浏览器引擎。Playwright 提供了一套强大的 API 来进行网页自动化测试&#xff0c;包括页面导航、元素选择、表单提交等操作&#xff0c;并且能够处理现代网页中的异步加载内…

【maven踩坑】一个坑 junit报错 但真正导致这个的不是junit的原因

目录 事件起因环境和工具操作过程解决办法结束语 事件起因 报错一&#xff1a; Internal Error occurred. org.junit.platform.commons.JUnitException: TestEngine with ID junit-vintage failed to discover tests报错二&#xff1a; Internal Error occurred. org.junit.pl…

ONNX: export failure: DLL load failed while importing _message: 找不到指定的程序。

ONNX: export failure 问题其他解决快速解决 问题 使用pytorch导出onnx&#xff08;Open Neural Network Exchange&#xff09;模型&#xff0c;结果使用conda安装完onnx之后&#xff0c;问题就出现了 ONNX: export failure: DLL load failed while importing _message: 找不到…