CLR類型設計之泛型(二)
在上一篇文章中,介紹了什麽是泛型,以及泛型和非泛型的區別,這篇文章主要講一些泛型的高級用法,泛型方法,泛型泛型接口和泛型委托,協變和逆變泛型類型參數和約束性,泛型的高級用法在平時的業務中用的不多,多用於封裝高級方法和一些底層封裝,前幾天讀了一篇文章,如何選擇網絡上的技術文章,因為現在關於技術的文章可以說非常多,但是時間是有限的,如果花很多時間閱讀了一篇文章卻沒有什麽用,豈不是很浪費時間,所以第一步選擇自己感興趣的文章閱讀,第二要把閱讀過的文章盡可能實現一次,讀書萬遍不如走上一遍,第三盡量不讀翻譯性的文章,這裏其實我覺得不是所有人都能很輕松的看懂官方文檔,所以這點還是仁者見仁,智者見智。為了讓文章盡可能的有深度,所以我覺得以後的博文中應該盡可能的貼出的知識模塊都有所解釋,有所理解。不是在網上復制粘貼以後在給別人看,博文不是筆記,所以要說出自己的見解
說了這麽多,那麽就開始務實的甩開膀子做吧!
泛型方法
既然講到了泛型是為了高級封裝,那麽我們就來封裝一個C#中的ORM吧,在封裝ORM之前還要有一個SQL幫助類,這個網上有很多,感興趣的可以直接到網上找一個,C#中封裝的ORM最好的Entity FromWork,感興趣的可以看下源碼,我們先看下下面的代碼,這是一個典型的泛型方法
1 /// <summary> 2 /// 查詢獲得一個實體 3 /// </summary> 4 /// <typeparam name="T"></typeparam>View Code5 /// <param name="sql"></param> 6 /// <param name="sqlParameters"></param> 7 /// <param name="transaction"></param> 8 /// <returns></returns> 9 public static T Get<T>(string sql, IList<SqlParameter> sqlParameters = null, IDbTransaction transaction = null) 10 { 11 DataTable table = SQLServerHelper.Query(sql, sqlParameters, transaction); 12 if (table != null && table.Rows != null && table.Rows.Count > 0) 13 { 14 DataRow row = table.Rows[0]; 15 return ConvertRowToModel<T>(row, table.Columns); 16 } 17 else 18 { 19 Type modeType = typeof(T); 20 return default(T); 21 } 22 23 }
註釋已經表明了這是用來獲取一個實體的Get<T>()方法中T定義為泛型類型,方法有一個必選參數兩個默認參數,string類型的sql語句,默認IList<SqlParameter> sqlParameters繼承自IList集合的SQL參數。這是參數化SQL的,每一個SQL語句都應該寫成參數化的,還有一個IDbTransaction transaction = null是否開啟事務,有了這樣三個參數就可以定義一個查詢指定SQL語句,是否有Where條件,是否開啟事務的方法,方法繼續執行,第是一行代碼是使用幫助類獲得數據,第14行和第15行是將獲得數據映射成對應的結構體,我們可以看下 ConvertRowToModel<T>(row, table.Columns)的內部實現
1 public static T ConvertRowToModel<T>(DataRow row, DataColumnCollection columns) 2 { 3 Type modeType = typeof(T); 4 Object model = Activator.CreateInstance(modeType); 5 6 foreach (var p in model.GetType().GetProperties()) 7 { 8 var propertyName = p.Name.ToUpper(); 9 var propertyType = p.PropertyType.Name; 10 if (columns.Contains(propertyName)) 11 { 12 var value = row[propertyName]; 13 14 if (propertyType.ToUpper().Contains("STRING")) 15 { 16 17 if (Convert.IsDBNull(value)) 18 { 19 value = string.Empty; 20 } 21 else 22 { 23 p.SetValue(model, value.ToString(), null); 24 } 25 } 26 else if (propertyType.ToUpper().Contains("INT")) 27 { 28 29 if (Convert.IsDBNull(value)) 30 { 31 value = 0; 32 } 33 34 p.SetValue(model, Int32.Parse(value.ToString()), null); 35 } 36 else if (propertyType.ToUpper().Contains("SINGLE")) 37 { 38 39 if (Convert.IsDBNull(value)) 40 { 41 value = 0.0f; 42 } 43 p.SetValue(model, Single.Parse(value.ToString()), null); 44 } 45 else if (propertyType.ToUpper().Contains("DATETIME")) 46 { 47 48 if (Convert.IsDBNull(value)) 49 { 50 value = DateTime.MinValue; 51 } 52 p.SetValue(model, DateTime.Parse(value.ToString()), null); 53 } 54 else if (propertyType.ToUpper().Contains("DOUBLE")) 55 { 56 57 if (Convert.IsDBNull(value)) 58 { 59 value = 0.0d; 60 } 61 p.SetValue(model, Double.Parse(value.ToString()), null); 62 } 63 else if (propertyType.ToUpper().Contains("BOOLEAN")) 64 { 65 66 if (Convert.IsDBNull(value)) 67 { 68 value = false; 69 } 70 if (value.GetType() == typeof(Int32)) 71 { 72 p.SetValue(model, Int32.Parse(value.ToString()) == 1, null); 73 74 } 75 else if (value.GetType() == typeof(String)) 76 { 77 p.SetValue(model, Boolean.Parse(value.ToString()), null); 78 } 79 else if (value.GetType() == typeof(Boolean)) 80 { 81 p.SetValue(model, (Boolean)(value), null); 82 } 83 84 } 85 else if (p.PropertyType.IsEnum)//Enum 86 { 87 if (Convert.IsDBNull(value) || string.IsNullOrEmpty(value.ToString())) 88 { 89 value = "0"; 90 } 91 92 p.SetValue(model, int.Parse(value.ToString()), null); 93 } 94 else if (propertyType.ToUpper().Contains("DECIMAL")) 95 { 96 97 if (Convert.IsDBNull(value)) 98 { 99 value = 0.0f; 100 } 101 p.SetValue(model, Decimal.Parse(value.ToString()), null); 102 } 103 104 } 105 } 106 return (T)model; 107 }View Code
這個方法有點長,但實際上很好理解,並且這個方法也是一個泛型方法,其中的關鍵點在於他會把所有的字段類型轉換成基礎的元類型,轉換成int,string這些最終會在返回給Get<T>()方法,這樣就完成了實體的映射,那麽有了上面兩個方法,就可以編寫簡單的ORM了,如果是增刪查改的話,就改變其中的邏輯,獲得返回的影響行數就可以了,那麽如何調用這個ORM呢,封裝後最主要的是使用。可以用如下方法直接調用
1 public static Student Getmodel(long id) { 2 StringBuilder sql = new StringBuilder(); 3 List<SqlParameter> args = new List<SqlParameter>(); 4 //根據學生id獲取學生信息 5 sql.Append("select * from student where [email protected]"); 6 //id參數化後賦值 7 args.Add(new SqlParameter("@id", id)); 8 //調用封裝ORM所在的CommonDao類 調用剛才的Get方法 9 //映射類Student,就會返回和Student類相符合的數據庫字段內容 10 return CommonDao.Get<Student>(sql.ToString(), args); 11 }View Code
是不是就簡單了很多呢。當然你也可以封裝的更深一些,這裏還是在傳遞sql語句,如果你喜歡Entitie fromwork那種方式,可以把Sql語句也作為固定的寫法,只傳遞where條件後面的參數就可以了,可以看到泛型方法很有用處
但是有的時候定義了泛型方法,卻希望他只能用於某一種特定的類型,上述的例子可以用於所有泛型類型,但是如果我要封裝的不是底層,只是某一個高級方法,只允許某一種類型使用這個方法,那麽該如何做呢?可以使用泛型約束
泛型約束
1 public static T WhereGet<T>(string sql, IList<SqlParameter> sqlParameters = null, IDbTransaction transaction = null) 2 where T:IList<T> 3 { 4 DataTable table = SQLServerHelper.Query(sql, sqlParameters, transaction); 5 if (table != null && table.Rows != null && table.Rows.Count > 0) 6 { 7 DataRow row = table.Rows[0]; 8 return ConvertRowToModel<T>(row, table.Columns); 9 } 10 else 11 { 12 Type modeType = typeof(T); 13 return default(T); 14 } 15 16 }View Code
CLR類型設計之泛型(二)