• Util应用程序框架公共操作类(二):数据类型转换公共操作类(源码篇)


      上一篇介绍了数据类型转换的一些情况,可以看出,如果不进行封装,有可能导致比较混乱的代码。本文通过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的一套开发流程,主要为了照顾还没有体验过单元测试的人,后面直接粘贴代码,以避免这样低效的叙述方式。

      单元测试代码如下。

      1 using System;
      2 using Microsoft.VisualStudio.TestTools.UnitTesting;
      3 
      4 namespace Util.Tests {
      5     /// <summary>
      6     /// 类型转换公共操作类测试
      7     /// </summary>
      8     [TestClass]
      9     public class ConvTest {
     10 
     11         #region ToInt(转换为整型)
     12 
     13         /// <summary>
     14         ///转换为整型,值为null
     15         ///</summary>
     16         [TestMethod]
     17         public void TestToInt_Null() {
     18             Assert.AreEqual( 0, Util.Conv.ToInt( null ) );
     19         }
     20 
     21         /// <summary>
     22         ///转换为整型,值为空字符串
     23         ///</summary>
     24         [TestMethod]
     25         public void TestToInt_Empty() {
     26             Assert.AreEqual( 0, Util.Conv.ToInt( "" ) );
     27         }
     28 
     29         /// <summary>
     30         ///转换为整型,无效值
     31         ///</summary>
     32         [TestMethod]
     33         public void TestToInt_Invalid() {
     34             Assert.AreEqual( 0, Util.Conv.ToInt( "1A" ) );
     35         }
     36 
     37         /// <summary>
     38         ///转换为整型,有效值
     39         ///</summary>
     40         [TestMethod]
     41         public void TestToInt() {
     42             Assert.AreEqual( 1, Util.Conv.ToInt( "1" ) );
     43             Assert.AreEqual( 1778020, Util.Conv.ToInt( "1778019.7801684" ) );
     44         }
     45 
     46         #endregion
     47 
     48         #region ToIntOrNull(转换为可空整型)
     49 
     50         /// <summary>
     51         ///转换为可空整型,值为null
     52         ///</summary>
     53         [TestMethod]
     54         public void TestToIntOrNull_Null() {
     55             Assert.IsNull( Util.Conv.ToIntOrNull( null ) );
     56         }
     57 
     58         /// <summary>
     59         ///转换为可空整型,值为空字符串
     60         ///</summary>
     61         [TestMethod]
     62         public void TestToIntOrNull_Empty() {
     63             Assert.IsNull( Util.Conv.ToIntOrNull( "" ) );
     64         }
     65 
     66         /// <summary>
     67         ///转换为可空整型,无效值
     68         ///</summary>
     69         [TestMethod]
     70         public void TestToIntOrNull_Invalid() {
     71             Assert.IsNull( Util.Conv.ToIntOrNull( "1A" ) );
     72         }
     73 
     74         /// <summary>
     75         ///转换为可空整型,值为0
     76         ///</summary>
     77         [TestMethod]
     78         public void TestToIntOrNull_0() {
     79             Assert.AreEqual( 0, Util.Conv.ToIntOrNull( "0" ) );
     80         }
     81 
     82         /// <summary>
     83         ///转换为可空整型,有效值
     84         ///</summary>
     85         [TestMethod]
     86         public void TestToIntOrNull() {
     87             Assert.AreEqual( 1, Util.Conv.ToIntOrNull( "1" ) );
     88         }
     89 
     90         #endregion
     91 
     92         #region ToDouble(转换为双精度浮点数)
     93 
     94         /// <summary>
     95         ///转换为双精度浮点数,值为null
     96         ///</summary>
     97         [TestMethod]
     98         public void TestToDouble_Null() {
     99             Assert.AreEqual( 0, Util.Conv.ToDouble( null ) );
    100         }
    101 
    102         /// <summary>
    103         ///转换为双精度浮点数,值为空字符串
    104         ///</summary>
    105         [TestMethod]
    106         public void TestToDouble_Empty() {
    107             Assert.AreEqual( 0, Util.Conv.ToDouble( "" ) );
    108         }
    109 
    110         /// <summary>
    111         ///转换为双精度浮点数,无效值
    112         ///</summary>
    113         [TestMethod]
    114         public void TestToDouble_Invalid() {
    115             Assert.AreEqual( 0, Util.Conv.ToDouble( "1A" ) );
    116         }
    117 
    118         /// <summary>
    119         ///转换为双精度浮点数,有效值
    120         ///</summary>
    121         [TestMethod]
    122         public void TestToDouble() {
    123             Assert.AreEqual( 1.2, Util.Conv.ToDouble( "1.2" ) );
    124         }
    125 
    126         /// <summary>
    127         /// 转换为双精度浮点数,指定2位小数位数
    128         ///</summary>
    129         [TestMethod()]
    130         public void TestToDouble_DigitsIs2() {
    131             Assert.AreEqual( 12.36, Util.Conv.ToDouble( "12.355", 2 ) );
    132         }
    133 
    134         #endregion
    135 
    136         #region ToDoubleOrNull(转换为可空双精度浮点数)
    137 
    138         /// <summary>
    139         ///转换为可空双精度浮点数,值为null
    140         ///</summary>
    141         [TestMethod]
    142         public void TestToDoubleOrNull_Null() {
    143             Assert.IsNull( Util.Conv.ToDoubleOrNull( null ) );
    144         }
    145 
    146         /// <summary>
    147         ///转换为可空双精度浮点数,值为空字符串
    148         ///</summary>
    149         [TestMethod]
    150         public void TestToDoubleOrNull_Empty() {
    151             Assert.IsNull( Util.Conv.ToDoubleOrNull( "" ) );
    152         }
    153 
    154         /// <summary>
    155         ///转换为可空双精度浮点数,无效值
    156         ///</summary>
    157         [TestMethod]
    158         public void TestToDoubleOrNull_Invalid() {
    159             Assert.IsNull( Util.Conv.ToDoubleOrNull( "1A" ) );
    160         }
    161 
    162         /// <summary>
    163         ///转换为可空双精度浮点数,值为0
    164         ///</summary>
    165         [TestMethod]
    166         public void TestToDoubleOrNull_0() {
    167             Assert.AreEqual( 0, Util.Conv.ToDoubleOrNull( "0" ) );
    168         }
    169 
    170         /// <summary>
    171         ///转换为可空双精度浮点数,有效值
    172         ///</summary>
    173         [TestMethod]
    174         public void TestToDoubleOrNull() {
    175             Assert.AreEqual( 1.2, Util.Conv.ToDoubleOrNull( "1.2" ) );
    176         }
    177 
    178         #endregion
    179 
    180         #region ToDecimal(转换为高精度浮点数)
    181 
    182         /// <summary>
    183         ///转换为高精度浮点数,值为null
    184         ///</summary>
    185         [TestMethod]
    186         public void TestToDecimal_Null() {
    187             Assert.AreEqual( 0, Util.Conv.ToDecimal( null ) );
    188         }
    189 
    190         /// <summary>
    191         ///转换为高精度浮点数,值为空字符串
    192         ///</summary>
    193         [TestMethod]
    194         public void TestToDecimal_Empty() {
    195             Assert.AreEqual( 0, Util.Conv.ToDecimal( "" ) );
    196         }
    197 
    198         /// <summary>
    199         ///转换为高精度浮点数,无效值
    200         ///</summary>
    201         [TestMethod]
    202         public void TestToDecimal_Invalid() {
    203             Assert.AreEqual( 0, Util.Conv.ToDecimal( "1A" ) );
    204         }
    205 
    206         /// <summary>
    207         ///转换为高精度浮点数,有效值
    208         ///</summary>
    209         [TestMethod]
    210         public void TestToDecimal() {
    211             Assert.AreEqual( 1.2M, Util.Conv.ToDecimal( "1.2" ) );
    212         }
    213 
    214         /// <summary>
    215         /// 转换为高精度浮点数,指定2位小数位数
    216         ///</summary>
    217         [TestMethod()]
    218         public void TestToDecimal_DigitsIs2() {
    219             Assert.AreEqual( 12.24M, Util.Conv.ToDecimal( "12.235", 2 ) );
    220         }
    221 
    222         #endregion
    223 
    224         #region ToDecimalOrNull(转换为可空高精度浮点数)
    225 
    226         /// <summary>
    227         ///转换为可空高精度浮点数,值为null
    228         ///</summary>
    229         [TestMethod]
    230         public void TestToDecimalOrNull_Null() {
    231             Assert.IsNull( Util.Conv.ToDecimalOrNull( null ) );
    232         }
    233 
    234         /// <summary>
    235         ///转换为可空高精度浮点数,值为空字符串
    236         ///</summary>
    237         [TestMethod]
    238         public void TestToDecimalOrNull_Empty() {
    239             Assert.IsNull( Util.Conv.ToDecimalOrNull( "" ) );
    240         }
    241 
    242         /// <summary>
    243         ///转换为可空高精度浮点数,无效值
    244         ///</summary>
    245         [TestMethod]
    246         public void TestToDecimalOrNull_Invalid() {
    247             Assert.IsNull( Util.Conv.ToDecimalOrNull( "1A" ) );
    248         }
    249 
    250         /// <summary>
    251         ///转换为可空高精度浮点数,无效值,指定2位小数位数
    252         ///</summary>
    253         [TestMethod]
    254         public void TestToDecimalOrNull_Invalid_DigitsIs2() {
    255             Assert.IsNull( Util.Conv.ToDecimalOrNull( "1A", 2 ) );
    256         }
    257 
    258         /// <summary>
    259         ///转换为可空高精度浮点数,值为0
    260         ///</summary>
    261         [TestMethod]
    262         public void TestToDecimalOrNull_0() {
    263             Assert.AreEqual( 0, Util.Conv.ToDecimalOrNull( "0" ) );
    264         }
    265 
    266         /// <summary>
    267         ///转换为可空高精度浮点数,有效值
    268         ///</summary>
    269         [TestMethod]
    270         public void TestToDecimalOrNull() {
    271             Assert.AreEqual( 1.2M, Util.Conv.ToDecimalOrNull( "1.2" ) );
    272         }
    273 
    274         /// <summary>
    275         /// 转换为可空高精度浮点数,指定2位小数位数
    276         ///</summary>
    277         [TestMethod()]
    278         public void ToDecimalOrNull_DigitsIs2() {
    279             Assert.AreEqual( 12.24M, Util.Conv.ToDecimalOrNull( "12.235", 2 ) );
    280         }
    281 
    282         #endregion
    283 
    284         #region ToGuid(转换为Guid)
    285 
    286         /// <summary>
    287         ///转换为Guid,值为null
    288         ///</summary>
    289         [TestMethod]
    290         public void TestToGuid_Null() {
    291             Assert.AreEqual( Guid.Empty, Util.Conv.ToGuid( null ) );
    292         }
    293 
    294         /// <summary>
    295         ///转换为Guid,值为空字符串
    296         ///</summary>
    297         [TestMethod]
    298         public void TestToGuid_Empty() {
    299             Assert.AreEqual( Guid.Empty, Util.Conv.ToGuid( "" ) );
    300         }
    301 
    302         /// <summary>
    303         ///转换为Guid,无效值
    304         ///</summary>
    305         [TestMethod]
    306         public void TestToGuid_Invalid() {
    307             Assert.AreEqual( Guid.Empty, Util.Conv.ToGuid( "1A" ) );
    308         }
    309 
    310         /// <summary>
    311         ///转换为Guid,有效值
    312         ///</summary>
    313         [TestMethod]
    314         public void TestToGuid() {
    315             Assert.AreEqual( new Guid( "B9EB56E9-B720-40B4-9425-00483D311DDC" ), Util.Conv.ToGuid( "B9EB56E9-B720-40B4-9425-00483D311DDC" ) );
    316         }
    317 
    318         #endregion
    319 
    320         #region ToGuidOrNull(转换为可空Guid)
    321 
    322         /// <summary>
    323         ///转换为可空Guid,值为null
    324         ///</summary>
    325         [TestMethod]
    326         public void TestToGuidOrNull_Null() {
    327             Assert.IsNull( Util.Conv.ToGuidOrNull( null ) );
    328         }
    329 
    330         /// <summary>
    331         ///转换为可空Guid,值为空字符串
    332         ///</summary>
    333         [TestMethod]
    334         public void TestToGuidOrNull_Empty() {
    335             Assert.IsNull( Util.Conv.ToGuidOrNull( "" ) );
    336         }
    337 
    338         /// <summary>
    339         ///转换为可空Guid,无效值
    340         ///</summary>
    341         [TestMethod]
    342         public void TestToGuidOrNull_Invalid() {
    343             Assert.IsNull( Util.Conv.ToGuidOrNull( "1A" ) );
    344         }
    345 
    346         /// <summary>
    347         ///转换为可空Guid,有效值
    348         ///</summary>
    349         [TestMethod]
    350         public void TestToGuidOrNull() {
    351             Assert.AreEqual( new Guid( "B9EB56E9-B720-40B4-9425-00483D311DDC" ), Util.Conv.ToGuidOrNull( "B9EB56E9-B720-40B4-9425-00483D311DDC" ) );
    352         }
    353 
    354         #endregion
    355 
    356         #region ToGuidList(转换为Guid集合)
    357 
    358         /// <summary>
    359         /// 转换为Guid集合,验证空字符串
    360         /// </summary>
    361         [TestMethod]
    362         public void TestToGuidList_Empty() {
    363             Assert.AreEqual( 0, Util.Conv.ToGuidList( "" ).Count );
    364         }
    365 
    366         /// <summary>
    367         /// 转换为Guid集合,验证最后为逗号
    368         /// </summary>
    369         [TestMethod]
    370         public void TestToGuidList_LastIsComma() {
    371             Assert.AreEqual( 1, Util.Conv.ToGuidList( "83B0233C-A24F-49FD-8083-1337209EBC9A," ).Count );
    372         }
    373 
    374         /// <summary>
    375         /// 转换为Guid集合,验证中间包含逗号
    376         /// </summary>
    377         [TestMethod]
    378         public void TestToGuidList_MiddleIsComma() {
    379             const string guid = "83B0233C-A24F-49FD-8083-1337209EBC9A,,EAB523C6-2FE7-47BE-89D5-C6D440C3033A,";
    380             Assert.AreEqual( 2, Util.Conv.ToGuidList( guid ).Count );
    381             Assert.AreEqual( new Guid( "83B0233C-A24F-49FD-8083-1337209EBC9A" ), Util.Conv.ToGuidList( guid )[0] );
    382             Assert.AreEqual( new Guid( "EAB523C6-2FE7-47BE-89D5-C6D440C3033A" ), Util.Conv.ToGuidList( guid )[1] );
    383         }
    384 
    385         /// <summary>
    386         /// 转换为Guid集合,仅1个Guid
    387         /// </summary>
    388         [TestMethod]
    389         public void TestToGuidList_1Guid() {
    390             const string guid = "83B0233C-A24F-49FD-8083-1337209EBC9A";
    391             Assert.AreEqual( 1, Util.Conv.ToGuidList( guid ).Count );
    392             Assert.AreEqual( new Guid( guid ), Util.Conv.ToGuidList( guid )[0] );
    393         }
    394 
    395         /// <summary>
    396         /// 转换为Guid集合,2个Guid
    397         /// </summary>
    398         [TestMethod]
    399         public void TestToGuidList_2Guid() {
    400             const string guid = "83B0233C-A24F-49FD-8083-1337209EBC9A,EAB523C6-2FE7-47BE-89D5-C6D440C3033A";
    401             Assert.AreEqual( 2, Util.Conv.ToGuidList( guid ).Count );
    402             Assert.AreEqual( new Guid( "83B0233C-A24F-49FD-8083-1337209EBC9A" ), Util.Conv.ToGuidList( guid )[0] );
    403             Assert.AreEqual( new Guid( "EAB523C6-2FE7-47BE-89D5-C6D440C3033A" ), Util.Conv.ToGuidList( guid )[1] );
    404         }
    405 
    406         #endregion
    407 
    408         #region ToDate(转换为日期)
    409 
    410         /// <summary>
    411         ///转换为日期,值为null
    412         ///</summary>
    413         [TestMethod]
    414         public void TestToDate_Null() {
    415             Assert.AreEqual( DateTime.MinValue, Util.Conv.ToDate( null ) );
    416         }
    417 
    418         /// <summary>
    419         ///转换为日期,值为空字符串
    420         ///</summary>
    421         [TestMethod]
    422         public void TestToDate_Empty() {
    423             Assert.AreEqual( DateTime.MinValue, Util.Conv.ToDate( "" ) );
    424         }
    425 
    426         /// <summary>
    427         ///转换为日期,无效值
    428         ///</summary>
    429         [TestMethod]
    430         public void TestToDate_Invalid() {
    431             Assert.AreEqual( DateTime.MinValue, Util.Conv.ToDate( "1A" ) );
    432         }
    433 
    434         /// <summary>
    435         ///转换为日期,有效值
    436         ///</summary>
    437         [TestMethod]
    438         public void TestToDate() {
    439             Assert.AreEqual( new DateTime( 2000, 1, 1 ), Util.Conv.ToDate( "2000-1-1" ) );
    440         }
    441 
    442         #endregion
    443 
    444         #region ToDateOrNull(转换为可空日期)
    445 
    446         /// <summary>
    447         ///转换为可空日期,值为null
    448         ///</summary>
    449         [TestMethod]
    450         public void TestToDateOrNull_Null() {
    451             Assert.IsNull( Util.Conv.ToDateOrNull( null ) );
    452         }
    453 
    454         /// <summary>
    455         ///转换为可空日期,值为空字符串
    456         ///</summary>
    457         [TestMethod]
    458         public void TestToDateOrNull_Empty() {
    459             Assert.IsNull( Util.Conv.ToDateOrNull( "" ) );
    460         }
    461 
    462         /// <summary>
    463         ///转换为可空日期,无效值
    464         ///</summary>
    465         [TestMethod]
    466         public void TestToDateOrNull_Invalid() {
    467             Assert.IsNull( Util.Conv.ToDateOrNull( "1A" ) );
    468         }
    469 
    470         /// <summary>
    471         ///转换为可空日期,有效值
    472         ///</summary>
    473         [TestMethod]
    474         public void TestToDateOrNull() {
    475             Assert.AreEqual( new DateTime( 2000, 1, 1 ), Util.Conv.ToDateOrNull( "2000-1-1" ) );
    476         }
    477 
    478         #endregion
    479 
    480         #region ToBool(转换为布尔值)
    481 
    482         /// <summary>
    483         ///转换为布尔值,值为null
    484         ///</summary>
    485         [TestMethod]
    486         public void TestToBool_Null() {
    487             Assert.AreEqual( false, Util.Conv.ToBool( null ) );
    488         }
    489 
    490         /// <summary>
    491         ///转换为布尔值,值为空字符串
    492         ///</summary>
    493         [TestMethod]
    494         public void TestToBool_Empty() {
    495             Assert.AreEqual( false, Util.Conv.ToBool( "" ) );
    496         }
    497 
    498         /// <summary>
    499         ///转换为布尔值,无效值
    500         ///</summary>
    501         [TestMethod]
    502         public void TestToBool_Invalid() {
    503             Assert.AreEqual( false, Util.Conv.ToBool( "1A" ) );
    504         }
    505 
    506         /// <summary>
    507         ///转换为布尔值,值为False
    508         ///</summary>
    509         [TestMethod]
    510         public void TestToBool_False() {
    511             Assert.AreEqual( false, Util.Conv.ToBool( "0" ) );
    512             Assert.AreEqual( false, Util.Conv.ToBool( "" ) );
    513             Assert.AreEqual( false, Util.Conv.ToBool( "no" ) );
    514             Assert.AreEqual( false, Util.Conv.ToBool( "No" ) );
    515             Assert.AreEqual( false, Util.Conv.ToBool( "false" ) );
    516             Assert.AreEqual( false, Util.Conv.ToBool( "False" ) );
    517         }
    518 
    519         /// <summary>
    520         ///转换为布尔值,值为True
    521         ///</summary>
    522         [TestMethod]
    523         public void TestToBool_True() {
    524             Assert.AreEqual( true, Util.Conv.ToBool( "1" ) );
    525             Assert.AreEqual( true, Util.Conv.ToBool( "" ) );
    526             Assert.AreEqual( true, Util.Conv.ToBool( "yes" ) );
    527             Assert.AreEqual( true, Util.Conv.ToBool( "Yes" ) );
    528             Assert.AreEqual( true, Util.Conv.ToBool( "true" ) );
    529             Assert.AreEqual( true, Util.Conv.ToBool( "True" ) );
    530         }
    531 
    532         #endregion
    533 
    534         #region ToBoolOrNull(转换为可空布尔值)
    535 
    536         /// <summary>
    537         ///转换为可空布尔值,值为null
    538         ///</summary>
    539         [TestMethod]
    540         public void TestToBoolOrNull_Null() {
    541             Assert.IsNull( Util.Conv.ToBoolOrNull( null ) );
    542         }
    543 
    544         /// <summary>
    545         ///转换为可空布尔值,值为空字符串
    546         ///</summary>
    547         [TestMethod]
    548         public void TestToBoolOrNull_Empty() {
    549             Assert.IsNull( Util.Conv.ToBoolOrNull( "" ) );
    550         }
    551 
    552         /// <summary>
    553         ///转换为可空布尔值,无效值
    554         ///</summary>
    555         [TestMethod]
    556         public void TestToBoolOrNull_Invalid() {
    557             Assert.IsNull( Util.Conv.ToBoolOrNull( "1A" ) );
    558         }
    559 
    560         /// <summary>
    561         ///转换为布尔值,值为False
    562         ///</summary>
    563         [TestMethod]
    564         public void TestToBoolOrNull_False() {
    565             Assert.AreEqual( false, Util.Conv.ToBoolOrNull( "0" ) );
    566             Assert.AreEqual( false, Util.Conv.ToBoolOrNull( "" ) );
    567             Assert.AreEqual( false, Util.Conv.ToBoolOrNull( "no" ) );
    568             Assert.AreEqual( false, Util.Conv.ToBoolOrNull( "No" ) );
    569             Assert.AreEqual( false, Util.Conv.ToBoolOrNull( "false" ) );
    570             Assert.AreEqual( false, Util.Conv.ToBoolOrNull( "False" ) );
    571         }
    572 
    573         /// <summary>
    574         ///转换为布尔值,值为True
    575         ///</summary>
    576         [TestMethod]
    577         public void TestToBoolOrNull_True() {
    578             Assert.AreEqual( true, Util.Conv.ToBoolOrNull( "1" ) );
    579             Assert.AreEqual( true, Util.Conv.ToBoolOrNull( "" ) );
    580             Assert.AreEqual( true, Util.Conv.ToBoolOrNull( "yes" ) );
    581             Assert.AreEqual( true, Util.Conv.ToBoolOrNull( "Yes" ) );
    582             Assert.AreEqual( true, Util.Conv.ToBoolOrNull( "true" ) );
    583             Assert.AreEqual( true, Util.Conv.ToBoolOrNull( "True" ) );
    584         }
    585 
    586         #endregion
    587 
    588         #region ToString(转换为字符串)
    589 
    590         /// <summary>
    591         ///转换为字符串,值为null
    592         ///</summary>
    593         [TestMethod]
    594         public void TestToString_Null() {
    595             Assert.AreEqual( string.Empty, Util.Conv.ToString( null ) );
    596         }
    597 
    598         /// <summary>
    599         ///转换为字符串,值为空字符串
    600         ///</summary>
    601         [TestMethod]
    602         public void TestToString_Empty() {
    603             Assert.AreEqual( string.Empty, Util.Conv.ToString( " " ) );
    604         }
    605 
    606         /// <summary>
    607         ///转换为字符串,有效值
    608         ///</summary>
    609         [TestMethod]
    610         public void TestToString() {
    611             Assert.AreEqual( "1", Util.Conv.ToString( 1 ) );
    612         }
    613 
    614         #endregion
    615 
    616         #region To(通用泛型转换)
    617 
    618         #region 目标为int
    619 
    620         /// <summary>
    621         ///通用泛型转换,目标为整数,值为null
    622         ///</summary>
    623         [TestMethod]
    624         public void TestTo_Int_Null() {
    625             Assert.AreEqual( 0, Conv.To<int>( null ) );
    626         }
    627 
    628         /// <summary>
    629         ///通用泛型转换,目标为整数,值为空字符串
    630         ///</summary>
    631         [TestMethod]
    632         public void TestTo_Int_Empty() {
    633             Assert.AreEqual( 0, Conv.To<int>( "" ) );
    634         }
    635 
    636         /// <summary>
    637         ///通用泛型转换,目标为整数,无效值
    638         ///</summary>
    639         [TestMethod]
    640         public void TestTo_Int_Invalid() {
    641             Assert.AreEqual( 0, Conv.To<int>( "1A" ) );
    642         }
    643 
    644         /// <summary>
    645         ///通用泛型转换,目标为整数,有效值
    646         ///</summary>
    647         [TestMethod]
    648         public void TestTo_Int() {
    649             Assert.AreEqual( 1, Conv.To<int>( "1" ) );
    650         }
    651 
    652         /// <summary>
    653         ///通用泛型转换,目标为可空整数,无效值
    654         ///</summary>
    655         [TestMethod]
    656         public void TestTo_IntOrNull_Invalid() {
    657             Assert.IsNull( Conv.To<int?>( "1A" ) );
    658         }
    659 
    660         /// <summary>
    661         ///通用泛型转换,目标为可空整数,有效值
    662         ///</summary>
    663         [TestMethod]
    664         public void TestTo_IntOrNull() {
    665             Assert.AreEqual( 1, Conv.To<int?>( "1" ) );
    666         }
    667 
    668         #endregion
    669 
    670         #region 目标为Guid
    671 
    672         /// <summary>
    673         ///通用泛型转换,目标为Guid,无效值
    674         ///</summary>
    675         [TestMethod]
    676         public void TestTo_Guid_Invalid() {
    677             Assert.AreEqual( Guid.Empty, Conv.To<Guid>( "1A" ) );
    678         }
    679 
    680         /// <summary>
    681         ///通用泛型转换,目标为Guid,有效值
    682         ///</summary>
    683         [TestMethod]
    684         public void TestTo_Guid() {
    685             Assert.AreEqual( new Guid( "B9EB56E9-B720-40B4-9425-00483D311DDC" ),
    686                 Conv.To<Guid>( "B9EB56E9-B720-40B4-9425-00483D311DDC" ) );
    687         }
    688 
    689         /// <summary>
    690         ///通用泛型转换,目标为可空Guid,无效值
    691         ///</summary>
    692         [TestMethod]
    693         public void TestTo_GuidOrNull_Invalid() {
    694             Assert.IsNull( Conv.To<Guid?>( "1A" ) );
    695         }
    696 
    697         /// <summary>
    698         ///通用泛型转换,目标为可空Guid,有效值
    699         ///</summary>
    700         [TestMethod]
    701         public void TestTo_GuidOrNull() {
    702             Assert.AreEqual( new Guid( "B9EB56E9-B720-40B4-9425-00483D311DDC" ),
    703                 Conv.To<Guid?>( "B9EB56E9-B720-40B4-9425-00483D311DDC" ) );
    704         }
    705 
    706         #endregion
    707 
    708         #region 目标为string
    709 
    710         /// <summary>
    711         ///通用泛型转换,目标为string,有效值
    712         ///</summary>
    713         [TestMethod]
    714         public void TestTo_String() {
    715             Assert.AreEqual( "123", Conv.To<string>( 123 ) );
    716         }
    717 
    718         #endregion
    719 
    720         #region 目标为double
    721 
    722         /// <summary>
    723         ///通用泛型转换,目标为double,无效值
    724         ///</summary>
    725         [TestMethod]
    726         public void TestTo_Double_Invalid() {
    727             Assert.AreEqual( 0, Conv.To<double>( "1A" ) );
    728         }
    729 
    730         /// <summary>
    731         ///通用泛型转换,目标为double,有效值
    732         ///</summary>
    733         [TestMethod]
    734         public void TestTo_Double() {
    735             Assert.AreEqual( 12.5, Conv.To<double>( "12.5" ) );
    736         }
    737 
    738         /// <summary>
    739         ///通用泛型转换,目标为可空double,无效值
    740         ///</summary>
    741         [TestMethod]
    742         public void TestTo_DoubleOrNull_Invalid() {
    743             Assert.IsNull( Conv.To<double?>( "1A" ) );
    744         }
    745 
    746         /// <summary>
    747         ///通用泛型转换,目标为可空double,有效值
    748         ///</summary>
    749         [TestMethod]
    750         public void TestTo_DoubleOrNull() {
    751             Assert.AreEqual( 12.5, Conv.To<double?>( "12.5" ) );
    752         }
    753 
    754         #endregion
    755 
    756         #region 目标为decimal
    757 
    758         /// <summary>
    759         ///通用泛型转换,目标为decimal,无效值
    760         ///</summary>
    761         [TestMethod]
    762         public void TestTo_Decimal_Invalid() {
    763             Assert.AreEqual( 0, Conv.To<decimal>( "1A" ) );
    764         }
    765 
    766         /// <summary>
    767         ///通用泛型转换,目标为decimal,有效值
    768         ///</summary>
    769         [TestMethod]
    770         public void TestTo_Decimal() {
    771             Assert.AreEqual( 12.5M, Conv.To<decimal>( "12.5" ) );
    772         }
    773 
    774         /// <summary>
    775         ///通用泛型转换,目标为可空decimal,无效值
    776         ///</summary>
    777         [TestMethod]
    778         public void TestTo_DecimalOrNull_Invalid() {
    779             Assert.IsNull( Conv.To<decimal?>( "1A" ) );
    780         }
    781 
    782         /// <summary>
    783         ///通用泛型转换,目标为可空decimal,有效值
    784         ///</summary>
    785         [TestMethod]
    786         public void TestTo_DecimalOrNull() {
    787             Assert.AreEqual( 12.5M, Conv.To<decimal?>( "12.5" ) );
    788         }
    789 
    790         #endregion
    791 
    792         #region 目标为bool
    793 
    794         /// <summary>
    795         ///通用泛型转换,目标为bool,无效值
    796         ///</summary>
    797         [TestMethod]
    798         public void TestTo_Bool_Invalid() {
    799             Assert.AreEqual( false, Conv.To<bool>( "1A" ) );
    800         }
    801 
    802         /// <summary>
    803         ///通用泛型转换,目标为bool,有效值
    804         ///</summary>
    805         [TestMethod]
    806         public void TestTo_Bool() {
    807             Assert.AreEqual( true, Conv.To<bool>( 1 ) );
    808         }
    809 
    810         /// <summary>
    811         ///通用泛型转换,目标为可空bool,无效值
    812         ///</summary>
    813         [TestMethod]
    814         public void TestTo_BoolOrNull_Invalid() {
    815             Assert.IsNull( Conv.To<bool?>( "1A" ) );
    816         }
    817 
    818         /// <summary>
    819         ///通用泛型转换,目标为可空bool,有效值
    820         ///</summary>
    821         [TestMethod]
    822         public void TestTo_BoolOrNull() {
    823             Assert.AreEqual( true, Conv.To<bool?>( "true" ) );
    824         }
    825 
    826         #endregion
    827 
    828         #region 目标为DateTime
    829 
    830         /// <summary>
    831         ///通用泛型转换,目标为DateTime,无效值
    832         ///</summary>
    833         [TestMethod]
    834         public void TestTo_DateTime_Invalid() {
    835             Assert.AreEqual( DateTime.MinValue, Conv.To<DateTime>( "1A" ) );
    836         }
    837 
    838         /// <summary>
    839         ///通用泛型转换,目标为DateTime,有效值
    840         ///</summary>
    841         [TestMethod]
    842         public void TestTo_DateTime() {
    843             Assert.AreEqual( new DateTime( 2000, 1, 1 ), Conv.To<DateTime>( "2000-1-1" ) );
    844         }
    845 
    846         /// <summary>
    847         ///通用泛型转换,目标为可空DateTime,无效值
    848         ///</summary>
    849         [TestMethod]
    850         public void TestTo_DateTimeOrNull_Invalid() {
    851             Assert.IsNull( Conv.To<DateTime?>( "1A" ) );
    852         }
    853 
    854         /// <summary>
    855         ///通用泛型转换,目标为可空DateTime,有效值
    856         ///</summary>
    857         [TestMethod]
    858         public void TestTo_DateTimeOrNull() {
    859             Assert.AreEqual( new DateTime( 2000, 1, 1 ), Conv.To<DateTime?>( "2000-1-1" ) );
    860         }
    861 
    862         #endregion
    863 
    864         #endregion
    865     }
    866 }
    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

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

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

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

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

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

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

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

      .Net应用程序框架交流QQ群: 386092459,欢迎有兴趣的朋友加入讨论。

      谢谢大家的持续关注,我的博客地址:http://www.cnblogs.com/xiadao521/

      下载地址: http://files.cnblogs.com/xiadao521/Util.2014.11.12.1.rar

  • 相关阅读:
    [转载]深入理解JavaScript闭包(closure)
    CSS Sprite初探之原理、使用
    动软,我被你迷惑了
    win7系统下CamtasiaStudio无法录上电脑声音只能录麦克风声音的解决办法
    [转载]向高级Javascript程序员阵营迈进:Javascript一些概念研究总结
    [转载]最简单的.NET生成随机数
    [转载]完全理解关键字this
    [转载]什么是高内聚、低耦合
    [转载]CSS背景:css background属性全解析
    [转载]HTML5语音输入(淘宝语音搜索)方法
  • 原文地址:https://www.cnblogs.com/xiadao521/p/4092846.html
Copyright © 2020-2023  润新知