数据类型转换公共操作类(介绍篇)

本系列文章将介绍一些对初学者有帮助的辅助类,这些辅助类本身并没有什么稀奇之处,如何能发现需要封装它们可能更加重要,所谓授之以鱼不如授之以渔,掌握封装公共操作类的技巧才是关键,我会详细说明创建这些类的动机和思考过程,以帮助初学者发现和封装自己需要的东西。创建公共操作类的技巧,大家可以参考我的这篇文章—— 应用程序框架实战十二:公共操作类开发技巧(初学者必读) 。

封装公共操作类,不仅要把技术上困难的封装进来,还需要不断观察自己的代码,以找出哪些部分可以更加简化。本文将介绍一个容易被大家所忽视的东西——数据类型转换。

数据类型转换可以把某个源类型转换为目标类型,比如把字符串转换为整型。一种选择是,你可以使用System.Convert类进行转换。

string input = "1";
int result = System.Convert.ToInt32( input);

这样看起来好像没什么问题,不过input很明显不一定是个常量,如果你还在使用ASP.NET Web Form这样的技术,你需要在后置代码中从控件读取值。

string input = TextBox1.Text;
int result = System.Convert.ToInt32( input);

当客户输入整数时,不会有什么问题,但他如果输入一个字母或汉字,上面代码会抛出一个异常,“System.FormatException: 输入字符串的格式不正确。”,这可能不是你想要的。当然,你可以在客户端进行JS验证,不过客户也可以绕过你的页面,直接POST到你的服务器,所以你在服务端必须处理这个问题。

引发异常,并且你没有进行任何处理,可能导致一个黄页,让你的客户一惊。为了避免显示黄页,初学者大多直接在代码上加一个try-catch进行捕获,并给客户一个友情提示。

try {
  string input = TextBox1.Text;
  int result = System.Convert.ToInt32( input );
}
catch( Exception ex ) {
  //弹出消息框提示客户输入正确数据
}

一旦吃到甜头,初学者发现这段代码可以实现他要的功能,不会过多考虑可维护性,于是会把这个结构向整个表现层复制,最终导致一个混乱的局面。

除了表现层以外,数据访问层也经常需要进行数据类型转换。如果你还在使用原始的Ado.Net,从DataReader获取值。

IDataReader reader = cmd.ExecuteReader();
int result = System.Convert.ToInt32( reader["字段名"] );

同样,为了不引发异常,初学者根据之前的经验,会在调用代码时添加try-catch进行异常捕获。

try {
  IDataReader reader = cmd.ExecuteReader();
  int result = System.Convert.ToInt32( reader["字段名"] );
}
catch( Exception ex ) {
  //有些人会在这里记录错误日志,还有些懒人直接留空,啥也不干
}        

所以最终的结果是,只要进行数据类型转换的操作,初学者为了一定的系统健壮性会大量添加异常处理结构,从而导致代码混乱。

另一个选择是,.Net提供了一个不引发异常的类型转换方法,比如,

string input = "1";
int result;
if( int.TryParse( input, out result ) == false ){
  //处理错误
}

TryParse会返回一个bool值,指示转换是否成功,如果转换失败,你可以记录日志,并显示一个错误。不过大部分人都可能会偷懒,不会在这里进行任何处理。

string input = "1";
int result;
int.TryParse( input, out result );

如果你不想引发异常,更不想用try-catch结构来捕获异常,这确实是一个更好的选择,但需要额外定义一个变量,作为out参数来获取值,会造成额外的工作量。

当然,.Net技术也一直处于持续改进中,表现层技术进入到MVC和WPF时代,而数据访问技术也进入到了Entity Framework时代。MVC提供了一个叫做模型绑定的功能,用于将界面上传回的数据映射到控制器操作的参数中,并且这些参数可以支持实体,这是一个非常强大的功能,这样就不需要手工进行赋值了,更不需要类型转换。WPF通过双向数据绑定,Entity Framework通过映射器,都解决了类似问题。

但是,并不是说数据类型转换就毫无用武之地了,考虑一个使用MVC的场景。你在界面上有一个表格,表格的每行都有一个checkbox,你可以打勾以选中某些行,然后你会把每行的实体标识传到控制器。如果实体标识的数据类型使用Guid,你可以用一个IList<Guid>来接收。

public ActionResult 方法名( IList<Guid> ids ) {
}

这一般都行得通。但是,你并不总是可以这样干,出于某些特殊原因,有时候你需要自己在js中用逗号拼接一些Id,或者用一个Hidden把用逗号拼接的Id保存起来,然后作为一个字符串传到控制器。比如” 06d8dcdd-cfad-433c-aec0-87bf613b9457, 684c0c02-93ec-4047-8f16-57e36b50d703”。

public ActionResult 方法名( string ids ) {
}

由于没有自动转换支持,你只好自己动手,这很简单,使用逗号把输入字符串打散成字符串数组,然后遍历每个数组元素,转换成Guid类型,再添加到一个结果集合保存起来就OK了。

public ActionResult 方法名( string ids ) {
  List<Guid> result = new List<Guid>();
  string[] list = ids.Split( ',' );
  foreach( var each in list ) {
    result.Add( Guid.Parse( each ) );
  }
  //在这里把result传到业务层
}

你发现这段代码不仅可以处理带有逗号的字符串,像” 06d8dcdd-cfad-433c-aec0-87bf613b9457, 684c0c02-93ec-4047-8f16-57e36b50d703”,甚至还能处理不带逗号的,比如“06d8dcdd-cfad-433c-aec0-87bf613b9457 “。你非常满意,然后把这一段代码复制到所有需要将拼接字符串转换成集合的地方。

没过多久,你在一个页面发现了Bug,以前测试的时候,都是操作多个Id,这次没进行操作,传过来的字符串是””,你的代码没有进行任何健壮性检测,所以失败了,抛出一个异常“无法识别的 GUID 格式。“。

你安慰自己“这并不算什么技术问题,只是一时疏忽,看我加一个判断,一招将它搞定“。不过在你的下意识里,已经感觉到进行边界测试才是健壮性的关键。

public ActionResult 方法名( string ids ) {
  List<Guid> result = new List<Guid>();
  if ( !string.IsNullOrWhiteSpace( ids ) ) {
    string[] list = ids.Split( ',' );
    foreach( var each in list ) {
      result.Add( Guid.Parse( each ) );
    }
  }
  //在这里把result传到业务层
}

你现在准备测试一下,传了一个””过来,果然有效,你大赞自己处理BUG的速度惊人,为了给其它地方也添加如此健壮的特性,你打开所有可能用到的页面,找到这几行代码,进行修改,虽然你感觉这确实有点无聊,但还是只有硬起头皮改了。

从上面可以看到,虽然是司空见惯的数据类型转换,也还是有很多值得我们改进的地方。

下一篇我将使用TDD方式把数据类型转换公共操作类开发出来。由于TDD并不是本系列介绍的重点,所以我不会在本文中详细介绍TDD的要点,请大家查看相关资料,如果有空,我会专门写一篇文章来分享我在使用TDD所碰到的障碍以及心得。

应用程序框架实战