开源 - Ideal库 - 常用罗列扩展方法(二)

打印 上一主题 下一主题

主题 846|帖子 846|积分 2538

书接上回,本日继续和大家享一些关于罗列操纵相关的常用扩展方法。

本日主要分享通过罗列值转换成罗列、罗列名称以及罗列形貌相关实现。
我们首先修改一下上一篇定义用来测试的正常罗列,新增一个罗列项,代码如下:
  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

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

渣渣兔

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表