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

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

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

  1. //正常枚举
  2. internal enum StatusEnum
  3. {
  4. [Description("正常")]
  5. Normal = 0,
  6. [Description("待机")]
  7. Standby = 1,
  8. [Description("离线")]
  9. Offline = 2,
  10. Online = 3,
  11. Fault = 4,
  12. }

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

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

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

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

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

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

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

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

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

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

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

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

  1. //存储枚举是否为位标志枚举
  2. private static readonly ConcurrentDictionary<Type, bool> _flags = new();
  3. //存储枚举对应掩码值
  4. private static readonly ConcurrentDictionary<Type, long> _flagsMasks = new();
  5. private static bool IsValidFlagsMask<TSource, TEnum>(TSource source)
  6. where TSource : struct
  7. where TEnum : struct, Enum
  8. {
  9. var type = typeof(TEnum);
  10. //判断是否为位标志枚举,如果有缓存直接获取,否则计算后存入缓存再返回
  11. var isFlags = _flags.GetOrAdd(type, (key) =>
  12. {
  13. //检查枚举类型是否有Flags特性
  14. return Attribute.IsDefined(key, typeof(FlagsAttribute));
  15. });
  16. //如果不是位标志枚举则返回false
  17. if (!isFlags)
  18. {
  19. return false;
  20. }
  21. //获取枚举掩码,如果有缓存直接获取,否则计算后存入缓存再返回
  22. var mask = _flagsMasks.GetOrAdd(type, (key) =>
  23. {
  24. //初始化存储掩码变量
  25. var mask = 0L;
  26. //获取枚举所有值
  27. var values = Enum.GetValues(key);
  28. //遍历所有枚举值,通过位或运算合并所有枚举值
  29. foreach (Enum enumValue in values)
  30. {
  31. //将枚举值转为long类型
  32. var valueLong = Convert.ToInt64(enumValue);
  33. // 过滤掉负数或无效的值,规范的位标志枚举应该都为非负数
  34. if (valueLong >= 0)
  35. {
  36. //合并枚举值至mask
  37. mask |= valueLong;
  38. }
  39. }
  40. //返回包含所有枚举值的枚举掩码
  41. return mask;
  42. });
  43. var value = Convert.ToInt64(source);
  44. //使用待验证值value和枚举掩码取反做与运算
  45. //结果等于0表示value为有效枚举值
  46. return (value & ~mask) == 0;
  47. }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

具体实现代码如下:

  1. [Fact]
  2. public void ToEnumByValue()
  3. {
  4. //正常枚举值,成功转换成枚举
  5. var status = 1.ToEnumByValue<StatusEnum>();
  6. Assert.Equal(StatusEnum.Standby, status);
  7. //不存在的枚举值,但是可以通过枚举项按位或合并得到,返回空
  8. var isStatusNull = 5.ToEnumByValue<StatusEnum>();
  9. Assert.Null(isStatusNull);
  10. //不存在的枚举值,也不可以通过枚举项按位或合并得到,返回空
  11. var isStatusNull1 = 8.ToEnumByValue<StatusEnum>();
  12. Assert.Null(isStatusNull1);
  13. //正常位标志枚举值,成功转换成枚举
  14. var flags = 3.ToEnumByValue<TypeFlagsEnum>();
  15. Assert.Equal(TypeFlagsEnum.HttpAndUdp, flags);
  16. //不存在的枚举值,但是可以通过枚举项按位或合并得到,成功转换成枚举
  17. var flagsGroup = 5.ToEnumByValue<TypeFlagsEnum>();
  18. Assert.Equal(TypeFlagsEnum.Http | TypeFlagsEnum.Tcp, flagsGroup);
  19. //不存在的枚举值,也不可以通过枚举项按位或合并得到,返回空
  20. var isFlagsNull = 8.ToEnumByValue<TypeFlagsEnum>();
  21. Assert.Null(isFlagsNull);
  22. }

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

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

  1. //根据枚举值转换成枚举,转换失败则返回默认枚举
  2. public static T? ToEnumOrDefaultByValue<T>(this int value, T defaultValue)
  3. where T : struct, Enum
  4. {
  5. //调用根据枚举值转换成枚举方法
  6. var result = value.ToEnumByValue<T>();
  7. if (result.HasValue)
  8. {
  9. //返回枚举
  10. return result.Value;
  11. }
  12. //转换失败则返回默认枚举
  13. return defaultValue;
  14. }

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

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

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

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

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

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

我们进行如下单元测试:

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

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

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

  1. //根据枚举值转换成枚举名称,转换失败则返回默认枚举名称
  2. public static string? ToEnumNameOrDefaultByValue<T>(this int value, string defaultValue)
  3. where T : struct, Enum
  4. {
  5. //调用根据枚举值转换成枚举名称方法
  6. var result = value.ToEnumNameByValue<T>();
  7. if (!string.IsNullOrWhiteSpace(result))
  8. {
  9. //返回枚举名称
  10. return result;
  11. }
  12. //转换失败则返回默认枚举名称
  13. return defaultValue;
  14. }

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

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

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

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

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

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

单元测试如下:

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

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

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

  1. //根据枚举值转换成枚举描述,转换失败则返回默认枚举描述
  2. public static string? ToEnumDescOrDefaultByValue<T>(this int value, string defaultValue)
  3. where T : struct, Enum
  4. {
  5. //调用根据枚举值转换成枚举描述方法
  6. var result = value.ToEnumDescByValue<T>();
  7. if (!string.IsNullOrWhiteSpace(result))
  8. {
  9. //返回枚举描述
  10. return result;
  11. }
  12. //转换失败则返回默认枚举描述
  13. return defaultValue;
  14. }

单元测试代码如下:

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

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

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

开源 - Ideal库 - 常用枚举扩展方法(二)的更多相关文章

  1. ES6 对象定义简写及常用的扩展方法

    1.ES6 对象定义简写 es6提供了对象定义里的属性,方法简写方式: 假如属性和变量名一样,可以省略,包括定义对象方法function也可以省略 <script type="text ...

  2. 枚举扩展方法获取枚举Description

    枚举扩展方法 /// <summary> /// 扩展方法,获得枚举的Description /// </summary> /// <param name="v ...

  3. C#枚举扩展方法,获取枚举值的描述值以及获取一个枚举类下面所有的元素

    /// <summary> /// 枚举扩展方法 /// </summary> public static class EnumExtension { private stat ...

  4. Farseer.net轻量级开源框架 中级篇:常用的扩展方法

    导航 目   录:Farseer.net轻量级开源框架 目录 上一篇:Farseer.net轻量级开源框架 中级篇: BasePage.BaseController.BaseHandler.BaseM ...

  5. [C#技术] .NET平台开源JSON库LitJSON的使用方法

    一个简单示例: String str = "{’name’:’cyf’,’id’:10,’items’:[{’itemid’:1001,’itemname’:’hello’},{’itemi ...

  6. .NET平台开源JSON库LitJSON的使用方法

    下载地址:LitJson.dll下载 一个简单示例: String str = "{'name':'cyf','id':10,'items':[{'itemid':1001,'itemnam ...

  7. C# LiNq的语法以及常用的扩展方法

    首先先来扯一下,这篇博文是我第一次写的,主要是我的一些摘录,希望对大家有所帮助. Linq的基础 •LINQ(读音link):Linq To SQL(过时).Linq To Object.Linq T ...

  8. (转).NET平台开源JSON库LitJSON的使用方法

    一个简单示例: String str = "{’name’:’cyf’,’id’:10,’items’:[{’itemid’:1001,’itemname’:’hello’},{’itemi ...

  9. 【EF学习笔记11】----------查询中常用的扩展方法

    先来看一下我们的表结构: 首先毫无疑问的要创建我们的上下文对象: using (var db = new Entities()) { //执行操作 } Average 平均值: //查询平均分 Con ...

  10. .NET常用的扩展方法整理

    using System; using System.Collections; using System.Collections.Generic; using System.Data; using S ...

随机推荐

  1. Python开发中,SQLAlchemy 的同步操作和异步操作封装,以及常规CRUD的处理。

    在我们使用Python来和数据库打交道中,SQLAlchemy是一个非常不错的ORM工具,通过它我们可以很好的实现多种数据库的统一模型接入,而且它提供了非常多的特性,通过结合不同的数据库驱动,我们可以 ...

  2. Unix、Linux、GNU 关系梳理

    之前写了一篇 MSYS2.MinGW 和 Cygwin 关系梳理的博客,但是要讲清它们几个的关系最好还是先了解一下操作系统的发展历程.遂补充了这篇博客. UNIX:现代操作系统的始祖 Operatin ...

  3. Ruby 学习笔记

    基本语法 变量 name = "Alice" age = 30 puts "Name: #{name}, Age: #{age}" var # 局部变量 @va ...

  4. 一次Java性能调优实践【代码+JVM 性能提升70%】

    这是我第一次对系统进行调优,涉及代码和JVM层面的调优.如果你能看到最后的话,或许会对你日常的开发有帮助,可以避免像我一样,犯一些低级别的错误.本次调优的代码是埋点系统中的报表分析功能,小公司,开发结 ...

  5. Vue3 动态子页面和菜单栏同步

    动态子页面 <router-view></router-view>显示子页面的内容 main.vue <template> <a-layout id=&quo ...

  6. ASP.NET Core – 操作 Uri 和 Query

    前言 以前就有写过了 Asp.net core 学习笔记 (操作 URL 和 Query), 但很乱, 这篇作为整理. Uri 介绍 结构: [Scheme]://[Host]:[Port][/Pat ...

  7. 29. GIL全局解释器锁、信号量、线程池进程池

    1. GIL全局解释器锁 1.1 概念 '''In CPython, the global interpreter lock, or GIL, is a mutex that prevents mul ...

  8. ShardingSphere系列(一)——ShardingSphere-JDBC初体验

    Apache ShardingSphere 是一套开源的分布式数据库解决方案组成的生态圈,它由 JDBC.Proxy 和 Sidecar(规划中)这 3 款既能够独立部署,又支持混合部署配合使用的产品 ...

  9. vant 2 的 toast

    因为toast使用的场景比较频繁,所以在 注册使用 Toast 的时候,直接在Vue实列的原型上添加了toast方便我们使用 : 格式:this.$toast.fail()      this.$to ...

  10. c#传统读取配置文件

    using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration.Json; namespace C ...