枚举类型无疑给我们开发带来了很大的便利,智能感应+代码可读性就让我们有足够的理由在项目中使用。但枚举类型使用不当也存在着温柔陷阱,而且陷阱很难发现。

      事情的缘起是这样的,项目A是公共基类,很多项目都会引用,项目B会引用项目A,但由于项目B使用人数不多而且功能很稳定,所以部署的时候一般不会传项目B的dll文件。但前前几天项目B突然出错了,报错的“找不到业务类型对应的数据库连接”。本地调试项目B,一点问题没有,可线上的就是出错,重新上传了项目B的dll,神奇般的好了。感觉有些莫名奇妙,一点都没改动,重新上传下dll就好了,是什么原因呢?没有无缘无故的爱,同样也没有无缘无故的错误,《温柔的枚举陷阱》一文帮我找到了答案,问题的根源是项目A中枚举类型的更改。

      我们可以从下面的示例中找到答案。ProjectA是很多项目共用的基础类库,ProjectB会添加ProjectA的引用,但二者并不在一个解决方案下面,更新ProjectA时会把A的dll文件放到B的bin目录下面。

项目ProjectA的代码示例
namespace ProjectA
{
/// <summary>
/// 业务类型
/// </summary>
public enum BusinessType
{
UserRead,
UserWrite,
ProductRead,
ProductWrite
}

public class DBHelper
{
/// <summary>
/// 根据业务类型获取连接字符串
/// </summary>
public static string GetConnectString(BusinessType bt)
{
string connectString = string.Empty;
switch (bt)
{
case BusinessType.UserRead:
connectString
= "UserRead";
break;
case BusinessType.UserWrite:
connectString
= "UserWrite";
break;
case BusinessType.ProductRead:
connectString
= "ProductRead";
break;
case BusinessType.ProductWrite:
connectString
= "ProductWrite";
break;
default:
connectString
= "UserRead";
break;
}
return connectString;
}
}
}

      项目B添加对项目A的引用后,其代码如下。

项目ProjectB代码示例
using ProjectA;

namespace ProjrctB
{
public class B
{
public static void GetUser(BusinessType bt)
{
//根据BusinessType选择合适的数据库获取数据
string connectString = DBHelper.GetConnectString(bt);
Console.WriteLine(connectString);
}
}
}

       执行ProjrctB.B.GetUser(BusinessType.ProductRead)后毫无疑问输出“ProductRead”,和我们预期的一致。接下来业务类型扩展了,要在BusinessType中增加评论类型,修改后的代码如下。

项目ProjectA业务类型扩展后的代码示例
namespace ProjectA
{
/// <summary>
/// 业务类型
/// </summary>
public enum BusinessType
{
CommentRead,
CommentWrite,
UserRead,
UserWrite,
ProductRead,
ProductWrite
}

public class DBHelper
{
/// <summary>
/// 根据业务类型获取连接字符串
/// </summary>
public static string GetConnectString(BusinessType bt)
{
string connectString = string.Empty;
switch (bt)
{
case BusinessType.CommentRead:
connectString
= "CommentRead";
break;
case BusinessType.CommentWrite:
connectString
= "CommentWrite";
break;
case BusinessType.UserRead:
connectString
= "UserRead";
break;
case BusinessType.UserWrite:
connectString
= "UserWrite";
break;
case BusinessType.ProductRead:
connectString
= "ProductRead";
break;
case BusinessType.ProductWrite:
connectString
= "ProductWrite";
break;
default:
connectString
= "UserRead";
break;
}
return connectString;
}
}
}

       重新生成ProjectA,将其dll部署到ProjectB的bin目录下面,这时候执行ProjrctB.B.GetUser(BusinessType.ProductRead)还会得到我们期待的结果吗?答案是否定的,执行后得到的结果是“UserRead”,并不是我们想要的“ProductRead”类型。为什么会出现这种情况呢?

       枚举值本质上就是个整数值,代码编译后,dll中只保留该枚举的整数值,而不会保留枚举变量名。如果枚举本身发生变化,如示例中在枚举BusinessType前面插入了两项,那么后面枚举变量的值都会改变的(如果不是显示指定值的话)。这个时候如果只重新编译了枚举本身,而没有编译引用枚举的地方ProjectB,那么就会出现张冠李戴的问题,因为整数值代表的枚举类型已经变了。这个问题解决的办法是创建枚举时显示指定枚举变量的值,扩展修改枚举类型时不要改变枚举变量的值。如下面的代码,添加业务类型时,将CommentRead = 4和CommentWrite = 5加入即可,而不要动以前的业务类型UserRead = 0,这样扩展类型时及时不去重新编译ProjectB,也不会出现上述问题了。

显示指定枚举变量的值
/// <summary>
/// 业务类型
/// </summary>
public enum BusinessType
{
CommentRead
= 4,
CommentWrite
= 5,
UserRead
= 0,
UserWrite
= 1,
ProductRead
= 2,
ProductWrite
= 3
}

        问题的本质在于编译时直接用变量的值代替了变量的名称,不仅枚举类型存在这个问题,常量同样也存在该问题,如果更新了常量的值,而没用编译引用到常量的项目,那么常量值的修改对引用到它的项目来说是无效的,因此有人建议除了值固定不变的常量(如圆周率),可以用readonly代替常量。

        如果不是看到类似问题的文章,这种问题很难找到答案,写出来对自己是一种提高,也希望能给遇到类似问题的朋友一些帮助。

 

 

 

posted on 2010-07-18 11:14  陈晨  阅读(3334)  评论(15编辑  收藏  举报