在开发过程中,经常需要将数据库中的整数值映射到C#中的枚举类型。尽管整数值是最简单的映射方式,但它并不是最直观的。例如,当处理包裹发送状态时,可能希望使用字符串值来表示状态,而不是数字。这样做的好处是代码更易于理解和维护。
假设有以下包裹发送状态的枚举:
public enum PackageStatus
{
RequestReceived = 0,
Accepted = 1,
Assembling = 2,
Sended = 3,
CustomerReceived = 4,
Completed = 5
}
这是一个有效的包裹发送流程。对于处理这种机制的系统来说,这种流程可能很容易记住。但随着公司和软件的增长,可能会出现更多的枚举类型。因此,最好的方法是将字符串和枚举类型结合起来,并允许将字符串(如"0"和"RequestReceived")转换为枚举值。
此外,还可以添加一个功能,使得这种转换不区分大小写。虽然这不是必需的,但这是一个不错的特性。除了与数据库接口之外,还有其他用例,例如:
接下来,将讨论如何实现这些功能。首先,需要检索枚举类型的值:
var values = Enum.GetValues(typeof(TEnum));
这是一个简单的静态方法,它返回枚举的所有可能值。然后,可以使用foreach循环遍历这些值:
public static TEnum GetEnumValue(string val)
{
var values = Enum.GetValues(typeof(TEnum));
foreach (TEnum value in values)
{
if (val.Equals(value.ToString()))
{
return value;
}
}
return default(TEnum);
}
这种方法有几个问题。首先,只能使用一个预定义的枚举类型。遗憾的是,在C#中无法创建一个通用的枚举方法。但可以做得很接近。第二个问题是,不需要在枚举类型中使用默认值0。0根本不能在枚举中!
对于第一个问题,可以添加一个泛型类型参数,并约束它实现枚举类型接口,并使用结构特殊约束。对于第二个问题,可以使用C#的default关键字。改进后的方法声明如下:
public static TEnum GetEnumValue(string val) where TEnum : struct, IComparable, IFormattable, IConvertible
{
var values = Enum.GetValues(typeof(TEnum));
foreach (TEnum value in values)
{
if (val.Equals(value.ToString()))
{
return value;
}
}
return default(TEnum);
}
当然,还有其他类型可以使用这种方法,但它们实际上不是枚举类型。这是C#中最好的解决方案(据所知)。如果字符串值在方法中找不到,default语句将返回枚举的第一个定义值。
下一步是添加整数值的可能性。为此,需要将枚举值转换为int类型。不能使用C#中定义的泛型类型进行简单的转换操作,但可以使用方法IConvertible的ToInt32方法。这需要一个格式提供程序来进行转换。使用了CultureInfo.CurrentCulture属性,这在应用程序中是可以的,但在其他应用程序中可能会有问题。这取决于它将在哪里使用。改进后的方法如下:
public static TEnum GetEnumValue2(string val) where TEnum : struct, IComparable, IFormattable, IConvertible
{
var values = Enum.GetValues(typeof(TEnum));
foreach (TEnum value in values)
{
if (val.Equals(value.ToString()) || val == (value.ToInt32(CultureInfo.CurrentCulture)).ToString())
{
return value;
}
}
return default(TEnum);
}
这基本上可以正常工作,但当它像这样使用时可能会有问题:
package.Status = GetEnumValue<PackageStatus>(newStatusString);
为什么?因为当newStatusString的值不是枚举的有效值时,status属性将重置为默认状态值。这可能是一个问题。解决方案可能是在值无效时抛出异常。这对于UI来说可能是好的。决定使用自定义默认值:
package.Status = GetEnumValue(newStatusString, package.Status);
这样,如果字符串中的值无效,状态将不会更改,旧值将被分配。
最后,为字符串比较添加了不区分大小写的功能。在.NET中有很多方法可以做到这一点,所以这应该根据将使用该代码的应用程序来考虑。例如,可以这样做:
public static TEnum GetEnumValue2(string val, TEnum current) where TEnum : struct, IComparable, IFormattable, IConvertible
{
var values = Enum.GetValues(typeof(TEnum));
foreach (TEnum value in values)
{
if (val.Equals(value.ToString(), StringComparison.OrdinalIgnoreCase) || val == (value.ToInt32(CultureInfo.CurrentCulture)).ToString())
{
return value;
}
}
return current;
}
一个很好的特性是将这个方法定义为字符串的扩展方法。这样,可以在写变量名后调用它,该变量的值是字符串值。
package.Status = newStatusString.GetEnumValue(package.Status);
更喜欢这样做,因为这对于编码风格来说更具有表达性。在为某种问题编写解决方案时,会想:想要这个值,但在映射后以这种方式。使用GetEnumValue方法作为普通方法而不是扩展方法,在看来,对于阅读代码的人来说是一个更大的负担(这通常是,总是想让生活更轻松:))。但这是另一篇文章的主题。
无论如何,这可以通过添加这个关键字并放置方法在单独的类中来实现:
public static class Extension
{
public static TEnum GetEnumValue(this string val, TEnum current) where TEnum : struct, IComparable, IFormattable, IConvertible
{
var values = Enum.GetValues(typeof(TEnum));
foreach (TEnum value in values)
{
if (val.Equals(value.ToString(), StringComparison.OrdinalIgnoreCase) || val == (value.ToInt32(CultureInfo.CurrentCulture)).ToString())
{
return value;
}
}
return current;
}
}