我们提供安全,免费的手游软件下载!
今天我想和大家分享一些关于枚举扩展设计思路和实现过程中遇到的难点。这些涉及到枚举的相关信息转换,比如枚举值、枚举名、枚举描述、枚举项、枚举类型等的转换。
01 、设计思路
设计思路其实很简单,就是通过枚举相关信息进行各种转换,比如通过枚举项获取枚举描述,通过枚举类型获取枚举名称-枚举描述键值对用于下拉列表等等。主要包括以下几类转换实现:
(1)通过枚举值转换为枚举、枚举名称、枚举描述;
(2)通过枚举名称转换为枚举、枚举值、枚举描述;
(3)通过枚举描述转换为枚举、枚举值、枚举名称;
(4)通过枚举项转换为枚举值、枚举描述;
(5)通过枚举类型转换为枚举值-枚举名称、枚举值-枚举描述、枚举名称-枚举值、枚举名称-枚举描述、枚举描述-枚举值、枚举描述-枚举名称等键值对集合;
02 、实现难点
在实现过程中遇到了一些难点以及需要注意的小细节。
1、枚举名称转枚举中的小细节
在实现枚举名称转枚举的过程中遇到了一个小坑。枚举本身提供了Enum.TryParse方法用于把枚举名称字符串或者枚举值字符串转为枚举,因此我们选用了此方法实现枚举名称转枚举。但是我们自己这个接口设计目标是把枚举名称转为枚举,因此需要排除误传枚举值字符串的情况。我们需要调用Enum.IsDefined方法检查枚举名称字符串是否是有效的枚举名称字符串,如果不是枚举中现有的有效枚举项,还需要考虑是否为位标志组合情况。
代码如下:
//根据枚举名称转换成枚举,转换失败则返回空
public static TEnum? ToEnumByName(this string name)
where TEnum : struct, Enum
{
if (Enum.TryParse(name, out var result))
{
if (Enum.IsDefined(typeof(TEnum), name))
{
return result;
}
else
{
var isValidFlags = IsValidFlagsMask(result.ToEnumValue());
return isValidFlags ? result : default(TEnum?);
}
}
return default;
}
2、枚举描述转枚举中的小细节
在实现枚举描述转枚举的过程中,对于带位标志枚举的处理需要特别小心。我们知道位标志枚举有一个特性是枚举项之间可以通过位操作进行组合得到一个有效的枚举项,而且这个枚举项还不存在于当前定义的枚举中。这就要求我们必须处理好组合情况。因此可以按照以下步骤进行处理:
(1)优先处理当枚举不带位标志的情况;
(2)再处理带位标志的情况;
位标志组合是通过英文逗号[,]拼接的,因此还要考虑到如果一个描述字符串中带有英文逗号[,],但是本身又不是组合的情况。
(3)先把描述当作不是组合情况,先处理一遍,如果转换成功则结束转换;
(4)如果是组合的情况,则计算出组合的每项枚举值,通过位操作得到其对应的枚举项;
而在组合的情况中还需要考虑,如果有组合的项时无效的情况,则这个枚举描述转换应该标记为无效。
(5)判断组合的每一项都是正确的,否则返回空;
(6)将通过组合计算出来的枚举项转为枚举;
具体代码如下:
//根据枚举描述转换成枚举,转换失败返回空
public static TEnum? ToEnumByDesc(this string description)
where TEnum : struct, Enum
{
var type = typeof(TEnum);
var info = GetEnumTypeInfo(type);
if (!info.IsFlags)
{
return ToEnumDesc(description);
}
var tenum = ToEnumDesc(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)
{
count++;
var valueLong = Convert.ToUInt64(value);
if (valueLong >= 0)
{
mask |= valueLong;
}
break;
}
}
}
if (count != names.Length)
{
return default;
}
var underlyingType = Enum.GetUnderlyingType(type);
if (underlyingType == typeof(byte))
{
return ((byte)mask).ToEnumByValue();
}
else if (underlyingType == typeof(sbyte))
{
return ((sbyte)mask).ToEnumByValue();
}
else if (underlyingType == typeof(short))
{
return ((short)mask).ToEnumByValue();
}
else if (underlyingType == typeof(ushort))
{
return ((ushort)mask).ToEnumByValue();
}
else if (underlyingType == typeof(int))
{
return ((int)mask).ToEnumByValue();
}
else if (underlyingType == typeof(uint))
{
return ((uint)mask).ToEnumByValue();
}
else if (underlyingType == typeof(long))
{
return ((long)mask).ToEnumByValue();
}
else if (underlyingType == typeof(ulong))
{
return mask.ToEnumByValue();
}
return default;
}
3、枚举转枚举值中的小细节
我们知道在定义枚举的时候可以指定枚举值类型为以下八种类型:sbyte、byte、short、ushort、int、uint、long、ulong。因此所有涉及到返回枚举值的方法,我们要考虑到支持不同类型的枚举值类型返回。同时我们也知道相同的方法名和入参,并不能通过不同的返回类型区分出不同的重载方法。因此我们可以通过泛型的方法支持不同类型的枚举值类型返回。
4、通过枚举值转换的小细节
通过上面我们知道枚举值类型有八种,因此涉及到枚举值转换出其他信息是需要同时兼容这八种类型。要实现这个功能,可以用上面提到的泛型。但是也引发了另一个问题,就是float、double、DateTime都是struct类型,这样就导致这些类型在编辑器中也可以点出ToEnumByValue等相关方法。因此我们选择另外一种方式:重载方法来实现,通过封装方法多写一些代码来实现对用户友好调用。
5、如何高效返回键值对数据
在通过枚举类型转成各种键值对集合时,有些方法需要用到反射,因此如何高效的获取这些信息就变成重中之重。最直接的想法是直接把每种枚举类型对应的键值对集合直接缓存起来,下次用到的时候直接从缓存中获取即可。但是仔细详细,一共有3个数据:枚举值、枚举名称、枚举描述。两两组合有6种情况,也就是要6个缓存存储12个数据,这样其实就相当于浪费了3倍的空间。因此,我们可以把这三个值存一份,然后在需要的时直接组合获取。枚举值和枚举名称可以考虑缓存,而枚举描述肯定需要缓存。
假如我们只缓存枚举描述,那么枚举值和枚举名称在使用的时候直接转换,那么我们执行要一份记录枚举描述的缓存以及一份记录枚举类型对应其所有枚举项的缓存即可。另外因为枚举对应的是否带位标志标记以及掩码可以同时记录下来。
6、如何识别一个枚举值是否为有效的位标志组合
如何识别一个枚举值是否是一个有效的位标志组合,可以说是整个代码中最难的部分了,其实前面也有提到,主要应用掩码的思想。稍晚些时候我会把库上传至Nuget,大家可以直接使用Ideal.Core.Common。
注 :测试方法代码以及示例源码都已经上传至代码库,有兴趣的可以访问https://gitee.com/hugogoos/Ideal。
热门资讯