數據類型轉換

from:http://www.javashuo.com/article/p-tdorrvhk-cq.htmlhtml

上一篇介紹了數據類型轉換的一些狀況,能夠看出,若是不進行封裝,有可能致使比較混亂的代碼。本文經過TDD方式把數據類型轉換公共操做類開發出來,並提供源碼下載。git

  咱們在 應用程序框架實戰十一:建立VS解決方案與程序集 一文已經建立了解決方案,包含一個類庫項目和一個單元測試項目。單元測試將使用.Net自帶的 MsTest,另外經過Resharper工具來觀察測試結果。程序員

  首先考慮咱們指望的API長成什麼樣子。基於TDD開發,其中一個做用是幫助程序員設計指望的API,這稱爲意圖導向編程。編程

  由於數據類型轉換是Convert,因此咱們先在單元測試項目中建立一個ConvertTest的類文件。 框架

  類建立好之後,我先隨便建立一個方法Test,以迅速展開工做。測試的方法名Test,我是隨便起的,由於如今還不清楚API是什麼樣,我一會再回過頭來改。ide

複製代碼
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,主要是想使用起來方便一點,而不是隻支持字符串參數,這可能致使裝箱和拆箱,從而形成一些性能損失,不過個人大多數項目在性能方面尚未這麼高的要求,因此這個損失對我來說無關痛癢。

  還有些數據類型轉換,我沒有放進來,主要是我平時不多用到,當我用到時再增長

相關文章
相關標籤/搜索