zoukankan      html  css  js  c++  java
  • 数据类型转换

    from:https://www.cnblogs.com/xiadao521/p/4092846.html

    上一篇介绍了数据类型转换的一些情况,可以看出,如果不进行封装,有可能导致比较混乱的代码。本文通过TDD方式把数据类型转换公共操作类开发出来,并提供源码下载。

      我们在 应用程序框架实战十一:创建VS解决方案与程序集 一文已经创建了解决方案,包含一个类库项目和一个单元测试项目。单元测试将使用.Net自带的 MsTest,另外通过Resharper工具来观察测试结果。

      首先考虑我们期望的API长成什么样子。基于TDD开发,其中一个作用是帮助程序员设计期望的API,这称为意图导向编程。

      因为数据类型转换是Convert,所以我们先在单元测试项目中创建一个ConvertTest的类文件。 

      类创建好以后,我先随便创建一个方法Test,以迅速展开工作。测试的方法名Test,我是随便起的,因为现在还不清楚API是什么样,我一会再回过头来改。

    复制代码
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    namespace Util.Tests {
        /// <summary>
        /// 类型转换公共操作类测试
        /// </summary>
        [TestClass]
        public class ConvertTest {
            [TestMethod]
            public void Test() {
            }
        }
    }     
    复制代码

      为了照顾还没有使用单元测试的朋友,我在这里简单介绍一下MsTest。MsTest是.Net仿照JUnit打造的一个单元测试框架。在单元测试类上需要添加一个TestClass特性,在测试方法上添加TestMethod特性,用来识别哪些类的操作需要测试。还有一些其它特性,在用到的时候我再介绍。

      现在先来实现一个最简单的功能,把字符串”1”转换为整数1。

    [TestMethod]
    public void Test() {
        Assert.AreEqual( 1, Util.ConvertHelper.ToInt( "1" ) );
    }

      我把常用公共操作类尽量放到顶级命名空间Util,这样我就可以通过编写Util.来弹出代码提示,这样我连常用类也不用记了。

      使用ConvertHelper是一个常规命名,大多数开发人员可以理解它是一个类型转换的公共操作类。我也这样用了多年,不过后面我发现Util.ConvertHelper有点啰嗦,所以我简化成Util.Convert,但Convert又和系统重名了,所以我现在使用Util.Conv,你不一定要按我的这个命名,你可以使用ConvertHelper这样的命名以提高代码清晰度。

      System.Convert使用ToInt32来精确表示int是一个32位的数字,不过我们的公共操作类不用这样精确,ToInt就可以了,如果要封装ToInt64呢,我就用ToLong,这样比较符合我的习惯。

      现在代码被简化成了下面的代码。

    Assert.AreEqual( 1, Util.Conv.ToInt( "1" ) );

      Assert在测试中用来断言,断言就是比较实际计算出来的值是否和预期一致,Assert包含大量比较方法,AreEqual使用频率最高,用来比较预期值(左边)与实际值(右边)是否值相等,还有一个AreSame方法用来比较是否引用相等。   

         

      由于Conv类还未创建,所以显示一个红色警告。   现在在Util类库项目中创建一个Conv类。

      创建了Conv类以后,单元测试代码检测到Conv,但ToInt方法未创建,所以红色警告转移到ToInt方法。

      现在用鼠标左键单击红色ToInit方法,Resharper在左侧显示一个红色的灯泡。

      单击红色灯泡提示,选择第一项”Create Method ‘Conv.ToInt’”。

      Resharper会在Conv类中自动创建一个ToInt方法。

    public class Conv {
        public static int ToInt( string s ) {
            throw new NotImplementedException();
         }
    }    

      方法体抛出一个未实现的异常,这正是我们想要的。TDD的口诀是“红、绿、重构”,第一步需要先保证方法执行失败,显示红色警告。至于未何需要测试先行,以及首先执行失败,牵扯TDD开发价值观,请大家参考相关资料。

      准备工作已经就绪,现在可以运行测试了。安装了Resharper以后,在添加了TestClass特性的左侧,会看见两个重叠在一起的圆形图标。另外,在TestMethod特性左侧,有一个黑白相间的圆形图标。

       单击Test方法左侧的图标,然后点击Run按钮。如果单击TestClass特性左侧的图标,会运行该类所有测试。

      测试开始运行,并显示红色警告,提示未实现的异常,第一步完成。

      为了实现功能,现在来添加ToInt方法的代码。

    public static int ToInt( string s ) {
        int result;
        int.TryParse( s, out result );
        return result;
    }

      再次运行测试,已经能够成功通过,第二步完成。 

      第三步是进行重构,现在看哪些地方可以重构。参数s看起来有点不爽,改成data,并添加XML注释。

    复制代码
            /// <summary>
            /// 转换为整型
            /// </summary>
            /// <param name="data">数据</param>
            public static int ToInt( string data ) {
                int result;
                int.TryParse( data, out result );
                return result;
            }    
    复制代码

      另外重构一下测试,为了更容易找到相关测试,一般测试文件名使用类名+Test,现在测试文件名改成ConvTest.cs,测试类名改成ConvTest。把测试方法名改成TestToInt,并添加XML注释。

    复制代码
            /// <summary>
            /// 测试转换为整型
            /// </summary>
            [TestMethod]
            public void TestToInt() {
                Assert.AreEqual( 1, Util.Conv.ToInt( "1" ) );
            }    
    复制代码

      关于测试的命名,很多著作都提出了自己不同的方法。在《.Net单元测试艺术》中,作者建议使用三部分进行组合命名。还有一些著作建议将测试内容用下划线分隔单词,拼成一个长句子,以方便阅读和理解。这可能对英文水平好的人很有效,不过我的英文水平很烂,我拿一些单词拼成一个长句以后,发现更难理解了。所以我所采用的测试方法命名可能不一定好,你可以按你容易理解的方式来命名。

      重构之后,需要重新测试代码,以观察是否导致失败。

      上面简单介绍了TDD的一套开发流程,主要为了照顾还没有体验过单元测试的人,后面直接粘贴代码,以避免这样低效的叙述方式。

      单元测试代码如下。

     ConvTest

      Conv类代码如下。

    复制代码
      1 using System;
      2 using System.Collections.Generic;
      3 using System.Linq;
      4 
      5 namespace Util {
      6     /// <summary>
      7     /// 类型转换
      8     /// </summary>
      9     public static class Conv {
     10 
     11         #region 数值转换
     12 
     13         /// <summary>
     14         /// 转换为整型
     15         /// </summary>
     16         /// <param name="data">数据</param>
     17         public static int ToInt( object data ) {
     18             if ( data == null )
     19                 return 0;
     20             int result;
     21             var success = int.TryParse( data.ToString(), out result );
     22             if ( success == true )
     23                 return result;
     24             try {
     25                 return Convert.ToInt32( ToDouble( data, 0 ) );
     26             }
     27             catch ( Exception ) {
     28                 return 0;
     29             }
     30         }
     31 
     32         /// <summary>
     33         /// 转换为可空整型
     34         /// </summary>
     35         /// <param name="data">数据</param>
     36         public static int? ToIntOrNull( object data ) {
     37             if ( data == null )
     38                 return null;
     39             int result;
     40             bool isValid = int.TryParse( data.ToString(), out result );
     41             if ( isValid )
     42                 return result;
     43             return null;
     44         }
     45 
     46         /// <summary>
     47         /// 转换为双精度浮点数
     48         /// </summary>
     49         /// <param name="data">数据</param>
     50         public static double ToDouble( object data ) {
     51             if ( data == null )
     52                 return 0;
     53             double result;
     54             return double.TryParse( data.ToString(), out result ) ? result : 0;
     55         }
     56 
     57         /// <summary>
     58         /// 转换为双精度浮点数,并按指定的小数位4舍5入
     59         /// </summary>
     60         /// <param name="data">数据</param>
     61         /// <param name="digits">小数位数</param>
     62         public static double ToDouble( object data, int digits ) {
     63             return Math.Round( ToDouble( data ), digits );
     64         }
     65 
     66         /// <summary>
     67         /// 转换为可空双精度浮点数
     68         /// </summary>
     69         /// <param name="data">数据</param>
     70         public static double? ToDoubleOrNull( object data ) {
     71             if ( data == null )
     72                 return null;
     73             double result;
     74             bool isValid = double.TryParse( data.ToString(), out result );
     75             if ( isValid )
     76                 return result;
     77             return null;
     78         }
     79 
     80         /// <summary>
     81         /// 转换为高精度浮点数
     82         /// </summary>
     83         /// <param name="data">数据</param>
     84         public static decimal ToDecimal( object data ) {
     85             if ( data == null )
     86                 return 0;
     87             decimal result;
     88             return decimal.TryParse( data.ToString(), out result ) ? result : 0;
     89         }
     90 
     91         /// <summary>
     92         /// 转换为高精度浮点数,并按指定的小数位4舍5入
     93         /// </summary>
     94         /// <param name="data">数据</param>
     95         /// <param name="digits">小数位数</param>
     96         public static decimal ToDecimal( object data, int digits ) {
     97             return Math.Round( ToDecimal( data ), digits );
     98         }
     99 
    100         /// <summary>
    101         /// 转换为可空高精度浮点数
    102         /// </summary>
    103         /// <param name="data">数据</param>
    104         public static decimal? ToDecimalOrNull( object data ) {
    105             if ( data == null )
    106                 return null;
    107             decimal result;
    108             bool isValid = decimal.TryParse( data.ToString(), out result );
    109             if ( isValid )
    110                 return result;
    111             return null;
    112         }
    113 
    114         /// <summary>
    115         /// 转换为可空高精度浮点数,并按指定的小数位4舍5入
    116         /// </summary>
    117         /// <param name="data">数据</param>
    118         /// <param name="digits">小数位数</param>
    119         public static decimal? ToDecimalOrNull( object data, int digits ) {
    120             var result = ToDecimalOrNull( data );
    121             if ( result == null )
    122                 return null;
    123             return Math.Round( result.Value, digits );
    124         }
    125 
    126         #endregion
    127 
    128         #region Guid转换
    129 
    130         /// <summary>
    131         /// 转换为Guid
    132         /// </summary>
    133         /// <param name="data">数据</param>
    134         public static Guid ToGuid( object data ) {
    135             if ( data == null )
    136                 return Guid.Empty;
    137             Guid result;
    138             return Guid.TryParse( data.ToString(), out result ) ? result : Guid.Empty;
    139         }
    140 
    141         /// <summary>
    142         /// 转换为可空Guid
    143         /// </summary>
    144         /// <param name="data">数据</param>
    145         public static Guid? ToGuidOrNull( object data ) {
    146             if ( data == null )
    147                 return null;
    148             Guid result;
    149             bool isValid = Guid.TryParse( data.ToString(), out result );
    150             if ( isValid )
    151                 return result;
    152             return null;
    153         }
    154 
    155         /// <summary>
    156         /// 转换为Guid集合
    157         /// </summary>
    158         /// <param name="guid">guid集合字符串,范例:83B0233C-A24F-49FD-8083-1337209EBC9A,EAB523C6-2FE7-47BE-89D5-C6D440C3033A</param>
    159         public static List<Guid> ToGuidList( string guid ) {
    160             var listGuid = new List<Guid>();
    161             if ( string.IsNullOrWhiteSpace( guid ) )
    162                 return listGuid;
    163             var arrayGuid = guid.Split( ',' );
    164             listGuid.AddRange( from each in arrayGuid where !string.IsNullOrWhiteSpace( each ) select new Guid( each ) );
    165             return listGuid;
    166         }
    167 
    168         #endregion
    169 
    170         #region 日期转换
    171 
    172         /// <summary>
    173         /// 转换为日期
    174         /// </summary>
    175         /// <param name="data">数据</param>
    176         public static DateTime ToDate( object data ) {
    177             if ( data == null )
    178                 return DateTime.MinValue;
    179             DateTime result;
    180             return DateTime.TryParse( data.ToString(), out result ) ? result : DateTime.MinValue;
    181         }
    182 
    183         /// <summary>
    184         /// 转换为可空日期
    185         /// </summary>
    186         /// <param name="data">数据</param>
    187         public static DateTime? ToDateOrNull( object data ) {
    188             if ( data == null )
    189                 return null;
    190             DateTime result;
    191             bool isValid = DateTime.TryParse( data.ToString(), out result );
    192             if ( isValid )
    193                 return result;
    194             return null;
    195         }
    196 
    197         #endregion
    198 
    199         #region 布尔转换
    200 
    201         /// <summary>
    202         /// 转换为布尔值
    203         /// </summary>
    204         /// <param name="data">数据</param>
    205         public static bool ToBool( object data ) {
    206             if ( data == null )
    207                 return false;
    208             bool? value = GetBool( data );
    209             if ( value != null )
    210                 return value.Value;
    211             bool result;
    212             return bool.TryParse( data.ToString(), out result ) && result;
    213         }
    214 
    215         /// <summary>
    216         /// 获取布尔值
    217         /// </summary>
    218         private static bool? GetBool( object data ) {
    219             switch ( data.ToString().Trim().ToLower() ) {
    220                 case "0":
    221                     return false;
    222                 case "1":
    223                     return true;
    224                 case "是":
    225                     return true;
    226                 case "否":
    227                     return false;
    228                 case "yes":
    229                     return true;
    230                 case "no":
    231                     return false;
    232                 default:
    233                     return null;
    234             }
    235         }
    236 
    237         /// <summary>
    238         /// 转换为可空布尔值
    239         /// </summary>
    240         /// <param name="data">数据</param>
    241         public static bool? ToBoolOrNull( object data ) {
    242             if ( data == null )
    243                 return null;
    244             bool? value = GetBool( data );
    245             if ( value != null )
    246                 return value.Value;
    247             bool result;
    248             bool isValid = bool.TryParse( data.ToString(), out result );
    249             if ( isValid )
    250                 return result;
    251             return null;
    252         }
    253 
    254         #endregion
    255 
    256         #region 字符串转换
    257 
    258         /// <summary>
    259         /// 转换为字符串
    260         /// </summary>
    261         /// <param name="data">数据</param>
    262         public static string ToString( object data ) {
    263             return data == null ? string.Empty : data.ToString().Trim();
    264         }
    265 
    266         #endregion
    267 
    268         #region 通用转换
    269 
    270         /// <summary>
    271         /// 泛型转换
    272         /// </summary>
    273         /// <typeparam name="T">目标类型</typeparam>
    274         /// <param name="data">数据</param>
    275         public static T To<T>( object data ) {
    276             if ( data == null || string.IsNullOrWhiteSpace( data.ToString() ) )
    277                 return default( T );
    278             Type type = Nullable.GetUnderlyingType( typeof( T ) ) ?? typeof( T );
    279             try {
    280                 if ( type.Name.ToLower() == "guid" )
    281                     return (T)(object)new Guid( data.ToString() );
    282                 if ( data is IConvertible )
    283                     return (T)Convert.ChangeType( data, type );
    284                 return (T)data;
    285             }
    286             catch {
    287                 return default( T );
    288             }
    289         }
    290 
    291         #endregion
    292     }
    293 }
    复制代码

      Conv公共操作类的用法,在单元测试中已经说得很清楚了,这也是单元测试的一个用途,即作为API说明文档。

      单元测试最强大的地方,可能是能够帮助你回归测试,如果你发现我的代码有BUG,请通知我一声,我只需要在单元测试中增加一个测试来捕获这个BUG,就可以永久修复它,并且由于采用TDD方式可以获得很高的测试覆盖率,所以我花上几秒钟运行一下全部测试,就可以知道这次修改有没有影响其它代码。这也是你创建自己的应用程序框架所必须要做的,它可以给你提供信心。

      可以看到,我在单元测试中进行了很多边界测试,比如参数为null或空字符串等。但不可能穷举所有可能出错的情况,因为可能想不到,另外时间有限,也不可能做到。当在项目上发现BUG后,再通过添加单元测试的方式修复BUG就可以了。由于你的项目代码调用的是应用程序框架API,所以你只需要在框架内修复一次,项目代码完全不动。

      像数据类型转换这样简单的操作,你发现写单元测试非常容易,因为它有明确的返回值,但如果没有返回值呢,甚至有外部依赖呢,那就没有这么简单了,需要很多技巧,所以你多看几本TDD和单元测试方面的著作有很多好处。

      另外,再补充一下,Conv这个类里面有几个法宝。一个是ToGuidList这个方法,当你需要把字符串转换为List<Guid>的时候就用它。还有一个泛型转换的方法To<T>,很多时候可以用它进行泛型转换。

      最后,我把所有方法参数类型都改成了object,主要是想使用起来方便一点,而不是只支持字符串参数,这可能导致装箱和拆箱,从而造成一些性能损失,不过我的大多数项目在性能方面还没有这么高的要求,所以这个损失对我来讲无关痛痒。

      还有些数据类型转换,我没有放进来,主要是我平时很少用到,当我用到时再增加

  • 相关阅读:
    【算法微解读】浅谈01分数规划
    【算法微解读】浅谈线段树
    近期目标
    【洛谷P5008 逛庭院】tarjan缩点+贪心
    【洛谷P1061 Jam的计数法】搜索
    【洛谷P1140 相似基因】动态规划
    【建兰普及模拟赛四】20181026
    【建兰普及模拟赛第三场】20181035
    【洛谷P2800又上锁妖塔】动态规划
    【建兰普及模拟赛第二场】20181024
  • 原文地址:https://www.cnblogs.com/liuqiyun/p/10910951.html
Copyright © 2011-2022 走看看