CLR類型設計之泛型(二)

            在上一篇文章中,介紹了什麼是泛型,以及泛型和非泛型的區別,這篇文章主要講一些泛型的高級用法,泛型方法,泛型接口和泛型委託,協變和逆變泛型類型參數和約束性,泛型的高級用法在平時的業務中用的很少,多用於封裝高級方法和一些底層封裝,前幾天讀了一篇文章,如何選擇網絡上的技術文章,由於如今關於技術的文章能夠說很是多,可是時間是有限的,若是花不少時間閱讀了一篇文章卻沒有什麼用,豈不是很浪費時間,因此第一步選擇本身感興趣的文章閱讀,第二要把閱讀過的文章儘量實現一次,讀書萬遍不如走上一遍,第三儘可能不讀翻譯性的文章,這裏其實我以爲不是全部人都能很輕鬆的看懂官方文檔,因此這點仍是仁者見仁,智者見智。爲了讓文章儘量的有深度,因此我以爲之後的博文中應該儘量的貼出的知識模塊都有所解釋,有所理解。不是在網上覆制粘貼之後在給別人看,博文不是筆記,因此要說出本身的看法sql

          說了這麼多,那麼就開始務實的甩開膀子作吧!數據庫

          泛型方法安全

             既然講到了泛型是爲了高級封裝,那麼咱們就來封裝一個C#中的ORM吧,在封裝ORM以前還要有一個SQL幫助類,這個網上有不少,感興趣的能夠直接到網上找一個,C#中封裝的ORM最好的Entity FromWork,感興趣的能夠看下源碼,咱們先看下下面的代碼,這是一個典型的泛型方法網絡

 1     /// <summary>
 2         /// 查詢得到一個實體
 3         /// </summary>
 4         /// <typeparam name="T"></typeparam>
 5         /// <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         }
View Code

            註釋已經代表了這是用來獲取一個實體的Get<T>()方法中T定義爲泛型類型,方法有一個必選參數兩個默認參數,string類型的sql語句,默認IList<SqlParameter> sqlParameters繼承自IList集合的SQL參數。這是參數化SQL的,每個SQL語句都應該寫成參數化的,還有一個IDbTransaction transaction = null是否開啓事務,有了這樣三個參數就能夠定義一個查詢指定SQL語句,是否有Where條件,是否開啓事務的方法,方法繼續執行,第是一行代碼是使用幫助類得到數據,第14行和第15行是將得到數據映射成對應的結構體,咱們能夠看下 ConvertRowToModel<T>(row, table.Columns)的內部實現ide

  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 id=@id");
 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條件後面的參數就能夠了,能夠看到泛型方法頗有用處學習

        可是有的時候定義了泛型方法,卻但願他只能用於某一種特定的類型,上述的例子能夠用於全部泛型類型,可是若是我要封裝的不是底層,只是某一個高級方法,只容許某一種類型使用這個方法,那麼該如何作呢?可使用泛型約束測試

        泛型約束ui

 

 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

       上面的代碼示例,能夠看到和第一次的寫法上同樣的,只是行2處多加了一個where T:IList<T>,這就限制了T只能是實現了IList接口的集合訪問。能夠看到泛型方法頗有用處,寫一個方法能夠適應不少類型,避免了同一套邏輯下的不一樣類型參數的重載。並且泛型不只僅支持方法,還支持接口和委託this

       泛型接口和泛型委託

     目的是同樣的,只是一種拓展,將泛型用於接口和委託上,可是若是沒有泛型接口,那麼每次用非泛型接口來操做值類型都會發生一次裝箱操做,所以C#提供了泛型接口的支持,來看下C#中提供的IList泛型接口

 1    //
 2     // 摘要:
 3     //     表示可按照索引單獨訪問的一組對象。
 4     //
 5     // 類型參數:
 6     //   T:
 7     //     列表中元素的類型。
 8     [DefaultMember("Item")]
 9     [TypeDependencyAttribute("System.SZArrayHelper")]
10     public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable
11     {
12         //
13         // 摘要:
14         //     獲取或設置位於指定索引處的元素。
15         //
16         // 參數:
17         //   index:
18         //     要得到或設置的元素從零開始的索引。
19         //
20         // 返回結果:
21         //     位於指定索引處的元素。
22         //
23         // 異常:
24         //   T:System.ArgumentOutOfRangeException:
25         //     index 不是 System.Collections.Generic.IList`1 中的有效索引。
26         //
27         //   T:System.NotSupportedException:
28         //     設置該屬性,並且 System.Collections.Generic.IList`1 爲只讀。
29         T this[int index] { get; set; }
30 
31         //
32         // 摘要:
33         //     肯定 System.Collections.Generic.IList`1 中特定項的索引。
34         //
35         // 參數:
36         //   item:
37         //     要在 System.Collections.Generic.IList`1 中定位的對象。
38         //
39         // 返回結果:
40         //     若是在列表中找到,則爲 item 的索引;不然爲 -1。
41         int IndexOf(T item);
42         //
43         // 摘要:
44         //     將一個項插入指定索引處的 System.Collections.Generic.IList`1。
45         //
46         // 參數:
47         //   index:
48         //     從零開始的索引,應在該位置插入 item。
49         //
50         //   item:
51         //     要插入到 System.Collections.Generic.IList`1 中的對象。
52         //
53         // 異常:
54         //   T:System.ArgumentOutOfRangeException:
55         //     index 不是 System.Collections.Generic.IList`1 中的有效索引。
56         //
57         //   T:System.NotSupportedException:
58         //     System.Collections.Generic.IList`1 爲只讀。
59         void Insert(int index, T item);
60         //
61         // 摘要:
62         //     移除指定索引處的 System.Collections.Generic.IList`1 項。
63         //
64         // 參數:
65         //   index:
66         //     要移除的項的從零開始的索引。
67         //
68         // 異常:
69         //   T:System.ArgumentOutOfRangeException:
70         //     index 不是 System.Collections.Generic.IList`1 中的有效索引。
71         //
72         //   T:System.NotSupportedException:
73         //     System.Collections.Generic.IList`1 爲只讀。
74         void RemoveAt(int index);
75     }
View Code

        咱們平時所寫的List集合,其實都默認實現了IList接口的行爲,在下一篇文章接口中,咱們就重點討論接口的行爲,這裏先不研究這個問題,先只看IList爲何是泛型接口,它實現了什麼?行10中 :public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable這句話代表了IList是一個接口,可訪問性公開繼承了ICollection,IEnumerable,IEnumerable的接口行爲和方法,裏面的代碼是其中的具體實現,好比行29:T this[int index] { get; set; },這個是被封裝成了屬性的方法,從返回結果中能夠看到這個屬性是爲了實現獲取位於指定索引處的元素。用法其實就是List.Index('要得到或設置的元素'),這就是C#中實現的一個泛型接口,那麼泛型委託又是什麼呢?

      CLR支持泛型委託,目的是保證任何類型對象都能以類型安全的方式傳給回掉方法,此外泛型委託容許值類型實例在傳給回掉方法時不進行任何裝箱,在C#中委託實際只提供了四個方法和一個類定義,4個方法包括一個構造器,一個Invoke方法,一個BeginInvoke方法和EndInvoke方法,若是定義的委託類型指定了類型參數,編譯器會定義委託類的方法,用指定的參數類型替換方法的參數類型和返回值類型。

     委託最大的做用就是爲類的時間綁定事件處理程序,但這和泛型委託不要緊,之後的文章中會有關於委託的詳細介紹,示例

 1     //
 2         // 摘要:
 3         //     基於謂詞篩選值序列。
 4         //
 5         // 參數:
 6         //   source:
 7         //     要篩選的 System.Collections.Generic.IEnumerable`1。
 8         //
 9         //   predicate:
10         //     用於測試每一個元素是否知足條件的函數。
11         //
12         // 類型參數:
13         //   TSource:
14         //     source 中的元素的類型。
15         //
16         // 返回結果:
17         //     一個 System.Collections.Generic.IEnumerable`1,包含輸入序列中知足條件的元素。
18         //
19         // 異常:
20         //   T:System.ArgumentNullException:
21         //     source 或 predicate 爲 null。
22         public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
View Code

    上述代碼看起來有點眼熟?沒錯這個就是Linq中得where。實際上Linq中得Where就是使用委託實現Func關鍵字就表明了委託

     委託和接口的逆變和協變泛型類型實參

            委託的每一個泛型類型參數均可標記爲協變量或逆變量,利用這個功能可將泛型委託類型的變量轉換爲相同的委託類型,可能不少人在開發過程當中不經常使用到,可是深刻的瞭解他們,確定是有好處的。咱們舉一個例子,

       List<汽車> 一羣汽車 = new List<汽車>();

       List<車子> 一羣車子 = 一羣汽車;
      顯然,上面那段代碼是會報錯的, 雖然汽車繼承於車子,能夠隱士轉換爲車子,可是List<汽車>並不繼承於List<車子>,因此上面的轉換,是行不通的。

          IEnumerable<汽車> 一羣汽車 = new List<汽車>();

          IEnumerable<車子> 一羣車子 = 一羣汽車;
          然而這樣倒是能夠的。那麼IEnumerable接口有什麼不一樣呢,更高級?可是IList繼承自IEnumerable,IEnumerable有的IList應該都有,可是IList有的IEnumerable不必定有,那麼爲啥IEnumerable能實現這種轉化?跟蹤到 IEnumerable接口發現其定義是這樣的  public interface IEnumerable<out T>,T前面多了一個out,其實是這個參數起了做用,在T前面聲明out表明這是一個協變變量,聲明in表明這是一個逆變變量,使用「out」,和「in」兩個關鍵字。可是隻能用在接口和委託上面,對泛型的類型進行聲明,當聲明爲「out」時,表明它是用來返回的,只能做爲結果返回,中途不能更改。當聲明爲"in"時,表明它是用來輸入的,只能做爲參數輸入,不能被返回。
           「協變」是指可以使用與原始指定的派生類型相比,派生程度更大的類型。 
            「逆變」則是指可以使用派生程度更小的類型。逆變,逆於常規的變。
           回到上面的例子,正由於「IEnumerable」接口聲明瞭out,因此,表明參數T只能被返回,中途不會被修改,因此,IEnumerable<車子> 一羣車子 = 一羣汽車;  這樣的強制轉換
 
是合法的,IL中其實是做了強制轉換的。

             總結:

                      泛型能夠說是一個很是重要的概念和點,在這裏有不少知識能夠學習,但學習和實際練習仍是有必定區別的,因此仍是要多寫一些代碼輔助理解

相關文章
相關標籤/搜索