.NET泛型解析

來源:互聯網
上載者:User

【1】:泛型介紹

泛型是C#2.0中一個重要的新特性,泛型是CLR和程式設計語言提供的一種特殊機制,它支援另一種形式的代碼重用。泛型通常用與集合以及作用於集合的方法一起使用,當然也可以單獨使用.

C#是一種強型別的語言,在泛型沒有被提出之前,我們在使用集合的代碼的時候,每次對其進行轉換都需要隱式的強制轉換,我們都知道所有對象的最終基類是object,我們在每次使用object的時候,無論是變換什麼類型都要對其進行強制轉換。
那麼有了泛型之後,使用泛型我們就無需進行轉換,因為泛型根據接收者傳入的參數類型,直接將每個類型更改為所傳入的參數類型.
一般情況下,建立泛型類的過程為:從一個現有的具體類開始,逐一將每個類型更改為型別參數,直至達到通用化和可用性的最佳平衡。 建立您自己的泛型類時,需要特別注意以下事項:

將哪些類型通用化為型別參數。

通常,能夠參數化類別型越多,代碼就會變得越靈活,重用性就越好。 但是,太多的通用化會使其他開發人員難以閱讀或理解代碼。

如果存在約束,應對型別參數應用什麼約束

一條有用的規則是,應用儘可能最多的約束,但仍使您能夠處理必須處理的類型。 例如,如果您知道您的泛型類僅用於參考型別,則應用類約束。 這可以防止您的類被意外地用於實值型別,並允許您對 T 使用 as 運算子以及檢查空值。

是否將泛型行為分解為基類和子類。

由於泛型類可以作為基類使用,此處適用的設計注意事項與非泛型類相同。 請參見本主題後面有關從泛型基類繼承的規則。

是否實現一個或多個泛型介面。

例如,如果您設計一個類,該類將用於建立基於泛型的集合中的項,則可能必須實現一個介面,如 IComparable,其中 T 是您的類的類型。

【2】:泛型的表示方式

泛型可以為參考型別和實值型別還有介面和委託,FCL( DLL程式集,包含了.NET架構下的各種DLL )類中定義了一個泛型列表,用來管理一個對象集合,如果我們想要使用這個泛型列表,可以在使用時指定具體資料類型。
泛型的表示為 “T” 如:List<T>, T 表示一個未指定的資料類型,我們可以看一下FCL類中泛型的引用定義:
System.Collections.Generic 命名空間包含定義泛型集合的介面和類,泛型集合允許使用者建立強型別集合,它能提供比非泛型強型別集合更好的型別安全和效能。
建立泛型類的過程為:從一個現有的具體類開始,逐一將每個類型更改為型別參數,直至達到通用化和可用性的最佳平衡

【3】:泛型的好處

1 : 使代碼更加的簡潔,清晰

從前面我們也提到了,泛型具備可重用性 , 減少我們代碼量, 使我們的程式更容易開發和維護,舉例 :
執行個體 1 : 在從資料庫中擷取資料庫的時候,我們經常會返回一個DataTable類型,然後將其轉換為List集合. 那麼如果沒有泛型的話,我會一般會這樣來做.

public List<TrainingUser>GetTrainingUser(string userId)        {            DataTable dt =                     SqliteHelper.ExecuteDataset(System.Data.CommandType.Text,                        @"                        SELECT DISTINCT UserId,TrainingId FROM TRAININGUSER AS TU                        INNER JOIN [USER] AS U                         ON U.ID = TU.USERID                         JOIN [TRAINING] AS T                        ON T.ID = TU.TRAININGID                        WHERE U.ID = '"+userId+"' AND T.ENDTIME > DATETIME('now', 'localtime') AND T.StartTime <= DATETIME('now', 'localtime') ;").Tables[0];            return DataTableToList(dt);        }         private List<TrainingUser> DataTableToList(DataTabledt)        {            List<TrainingUser> list = new List<TrainingUser>();            if(dt. Rows.Count > 0 )            {                foreach (DataRow row in dt .Rows)                {                    TrainingUser trainingUser = new TrainingUser();                    if(row["UserId" ] != null)                    {                        trainingUser .UserId = row["UserId"].ToString();                    }                    if(row["TrainingId" ] != null)                    {                        trainingUser.TrainingId = row["TrainingId"].ToString();                    }                    list.Add(trainingUser);                }            }            return list;        }

在方法DataTableToList中,我們傳入了一個DataTable的對象,然後在去迴圈遍曆每一行的對象值從而去賦值給TrainingUser類型對象,這隻是其中的一個方法,如果我們的類型還有 Training/User/Project等類型的話,我們是不是就要寫很多如同DataTableToList這樣的方法呢? 這就體現出了這樣的方式,會造成代碼的冗餘以及維護不便問題,那麼我們使用泛型來解決

執行個體 2 : 使用泛型使上面的代碼更見的清晰,簡潔

public static List<T> ToList1<T>(DataTable dt) whereT : class, new()        {            var prlist =new List<PropertyInfo>();            Type type = typeof(T);            Array.ForEach(                type.GetProperties(),                p =>                {                    if(dt.Columns.IndexOf(p.Name) !=-1)                    {                        prlist.Add(p);                    }                });            var oblist = new List<T>();             // System.Data.SqlTypes.            foreach(DataRow row in dt.Rows)            {                var ob = new T();                prlist.ForEach(                    p =>                    {                        if(row[p.Name] != DBNull.Value)                        {                            p.SetValue(ob, row[p.Name], null);                        }                    });                oblist.Add(ob);            }             return oblist;        }

在上面的這個方法中,我們定義了一個泛型方法,內部實現中是使用了反射的原理,將DataTable轉換為了List(反射後續隨筆中總結,此處只關注泛型部分即可),我們定義了一個靜態傳回值為List<T> ,前面我們說過 T : 代表任意類型(枚舉除外),ToList1<T>,說明我們在調用這個方法的時候,同時要賦予方法名一個類型值,這個類型要和它的傳回值類型一致(泛型是型別安全的),Where : 用於限制T的條件 ,例如 where T : class,new() 表示 T 只能是一個類,或者一個類型對象,那麼我們在調用的時候就可以這樣來

public List<TrainingUser>GetTrainingIdByUserId(string userId)        {              List<TrainingUser> trainingUserList =  DataTableHelper.ToList1<TrainingUser>(                    SqliteHelper.ExecuteDataset(System.Data.CommandType.Text,                        @"                        SELECT DISTINCT UserId,TrainingId FROM TRAININGUSER AS TU                        INNER JOIN [USER] AS U                         ON U.ID = TU.USERID                         JOIN [TRAINING] AS T                        ON T.ID = TU.TRAININGID                        WHERE U.ID = '"+ userId +"' AND T.ENDTIME > DATETIME('now', 'localtime') AND T.StartTime <= DATETIME('now', 'localtime') ;").Tables[0]);              return trainingUserList ;        }

代碼中的DataTableHelper.ToList1<TrainingUser> 即為我們剛才所寫的一個泛型方法,這樣我們可以看到,ToList<T> 傳入的類型為TrainingUser,同時接收者為:List<T> = List<TrainingUser> ,
這樣即便我們後續還有Training/User/Project等其他的類型,我們都可以直接使用DataTableHelper.ToList1<T>(DataTable dt) 來進行類型轉換.

2 : 提升程式的效能

泛型與非泛型相比較而言,效能要好一些,這是為什麼? 首先,泛型的類型是由調用者(接收者),去直接賦值的(型別安全的), 那麼就不會存在類型轉換的問題,其次, 泛型減少了裝箱和拆箱的過程.
執行個體 3 : 對於實值型別泛型與非泛型的效能比較

private static void ListTest()        {            List<int>list = new List<int>();            for(inti = 0; i < 100; i++)            {                list.Add(i);                int a = list[i];            }            list =null;        }        private static void ArrListTest()        {            ArrayList arr = new ArrayList();            for(inti = 0; i <100; i++)            {                arr.Add(i);                int s = (int)arr[i];            }            arr = null;        }              Stopwatch sw = new Stopwatch();            sw.Start();            ListTest();            Console.WriteLine(" 使用泛型List執行實值型別方法曆時 :  "+ sw.Elapsed.ToString());            sw.Stop();             Stopwatch sw1 = new Stopwatch();            sw1.Start();            ArrListTest();            Console.WriteLine(" 使用非泛型ArrayList執行實值型別方法曆時 :  "+ sw1.Elapsed.ToString());            sw1.Stop();            Console.ReadLine();

通過迴圈 100 來比較,結果為 :

我們可以看到非泛型的時間要比泛型的時間多出0.0000523秒,泛型比非泛型的時間要多出一些, 那麼我們將數值改動一下改為迴圈 1000次.得出結果為 :

泛型比非泛型執行的時間要短0.0000405秒
我們將時間在改動一下,改為 100000呢?結果為 :

這次差距為 0.0054621 並且隨著執行次數的增長,非泛型相比泛型的時間會逐步的增加,
通過反編譯我們也能看出 :
泛型:

非泛型

從編譯中我們也能看出泛型方法中,接收的為Int32,非泛型為Object,其次泛型不會進行裝箱和拆箱操作,非泛型每次執行都要進行裝箱和拆箱操作.

3 : 型別安全

在執行個體1 , 2 ,3 中我們都有備忘說明,泛型的發送著必須要和接收者進行一致,否則會報異常 ,例如 :

執行個體 4 :

首頁

最新文章

IT 職場

前端

後端

移動端

資料庫

營運

其他技術

- 導航條 -首頁最新文章IT 職場前端- JavaScript- HTML5- CSS後端- Python- Java- C/C++- PHP- .NET- Ruby- Go移動端- Android- iOS資料庫營運- Linux- UNIX其他技術- Git- 機器學習- 演算法- 測試- 資訊安全- Vim

伯樂線上 > 首頁 > 所有文章 > 開發 > .NET泛型解析(上)

.NET泛型解析(上)

2015/07/03 · 開發 · .Net

分享到:6


拍攝與剪輯“懷孕日記”

PS大神通關教程

PS入門基礎-魔幻調色

慕課網技術沙龍之前端專場


原文出處: 劉彬的部落格

【1】:泛型介紹

泛型是C#2.0中一個重要的新特性,泛型是CLR和程式設計語言提供的一種特殊機制,它支援另一種形式的代碼重用。泛型通常用與集合以及作用於集合的方法一起使用,當然也可以單獨使用.

C#是一種強型別的語言,在泛型沒有被提出之前,我們在使用集合的代碼的時候,每次對其進行轉換都需要隱式的強制轉換,我們都知道所有對象的最終基類是object,我們在每次使用object的時候,無論是變換什麼類型都要對其進行強制轉換。
那麼有了泛型之後,使用泛型我們就無需進行轉換,因為泛型根據接收者傳入的參數類型,直接將每個類型更改為所傳入的參數類型.
一般情況下,建立泛型類的過程為:從一個現有的具體類開始,逐一將每個類型更改為型別參數,直至達到通用化和可用性的最佳平衡。 建立您自己的泛型類時,需要特別注意以下事項:

將哪些類型通用化為型別參數。

通常,能夠參數化類別型越多,代碼就會變得越靈活,重用性就越好。 但是,太多的通用化會使其他開發人員難以閱讀或理解代碼。

如果存在約束,應對型別參數應用什麼約束

一條有用的規則是,應用儘可能最多的約束,但仍使您能夠處理必須處理的類型。 例如,如果您知道您的泛型類僅用於參考型別,則應用類約束。 這可以防止您的類被意外地用於實值型別,並允許您對 T 使用 as 運算子以及檢查空值。

是否將泛型行為分解為基類和子類。

由於泛型類可以作為基類使用,此處適用的設計注意事項與非泛型類相同。 請參見本主題後面有關從泛型基類繼承的規則。

是否實現一個或多個泛型介面。

例如,如果您設計一個類,該類將用於建立基於泛型的集合中的項,則可能必須實現一個介面,如 IComparable,其中 T 是您的類的類型。

【2】:泛型的表示方式

泛型可以為參考型別和實值型別還有介面和委託,FCL( DLL程式集,包含了.NET架構下的各種DLL )類中定義了一個泛型列表,用來管理一個對象集合,如果我們想要使用這個泛型列表,可以在使用時指定具體資料類型。
泛型的表示為 “T” 如:List<T>, T 表示一個未指定的資料類型,我們可以看一下FCL類中泛型的引用定義:
System.Collections.Generic 命名空間包含定義泛型集合的介面和類,泛型集合允許使用者建立強型別集合,它能提供比非泛型強型別集合更好的型別安全和效能。
建立泛型類的過程為:從一個現有的具體類開始,逐一將每個類型更改為型別參數,直至達到通用化和可用性的最佳平衡

【3】:泛型的好處

1 : 使代碼更加的簡潔,清晰

從前面我們也提到了,泛型具備可重用性 , 減少我們代碼量, 使我們的程式更容易開發和維護,舉例 :
執行個體 1 : 在從資料庫中擷取資料庫的時候,我們經常會返回一個DataTable類型,然後將其轉換為List集合. 那麼如果沒有泛型的話,我會一般會這樣來做.

C#

public List<TrainingUser>GetTrainingUser(string userId)        {            DataTable dt =                     SqliteHelper.ExecuteDataset(System.Data.CommandType.Text,                        @"                        SELECT DISTINCT UserId,TrainingId FROM TRAININGUSER AS TU                        INNER JOIN [USER] AS U                         ON U.ID = TU.USERID                        JOIN [TRAINING] AS T                        ON T.ID = TU.TRAININGID                        WHERE U.ID = '"+userId+"' AND T.ENDTIME > DATETIME('now', 'localtime') AND T.StartTime <= DATETIME('now', 'localtime') ;").Tables[0];            return DataTableToList(dt);        }         private List<TrainingUser> DataTableToList(DataTabledt)        {            List<TrainingUser> list = new List<TrainingUser>();            if(dt. Rows.Count > 0 )            {                foreach (DataRow row in dt .Rows)                {                    TrainingUser trainingUser = new TrainingUser();                    if(row["UserId" ] != null)                    {                        trainingUser .UserId = row["UserId"].ToString();                    }                    if(row["TrainingId" ] != null)                    {                        trainingUser.TrainingId = row["TrainingId"].ToString();                    }                    list.Add(trainingUser);                }            }            return list;        }

在方法DataTableToList中,我們傳入了一個DataTable的對象,然後在去迴圈遍曆每一行的對象值從而去賦值給TrainingUser類型對象,這隻是其中的一個方法,如果我們的類型還有 Training/User/Project等類型的話,我們是不是就要寫很多如同DataTableToList這樣的方法呢? 這就體現出了這樣的方式,會造成代碼的冗餘以及維護不便問題,那麼我們使用泛型來解決

執行個體 2 : 使用泛型使上面的代碼更見的清晰,簡潔

C#

public static List<T> ToList1<T>(DataTable dt) whereT : class, new()        {            var prlist =new List<PropertyInfo>();            Type type = typeof(T);            Array.ForEach(                type.GetProperties(),                p =>                {                    if(dt.Columns.IndexOf(p.Name) !=-1)                    {                        prlist.Add(p);                    }                });            var oblist = new List<T>();             // System.Data.SqlTypes.            foreach(DataRow row in dt.Rows)            {                var ob = new T();                prlist.ForEach(                    p =>                    {                        if(row[p.Name] != DBNull.Value)                        {                            p.SetValue(ob, row[p.Name], null);                        }                    });                oblist.Add(ob);            }             return oblist;        }

在上面的這個方法中,我們定義了一個泛型方法,內部實現中是使用了反射的原理,將DataTable轉換為了List(反射後續隨筆中總結,此處只關注泛型部分即可),我們定義了一個靜態傳回值為List<T> ,前面我們說過 T : 代表任意類型(枚舉除外),ToList1<T>,說明我們在調用這個方法的時候,同時要賦予方法名一個類型值,這個類型要和它的傳回值類型一致(泛型是型別安全的),Where : 用於限制T的條件 ,例如 where T : class,new() 表示 T 只能是一個類,或者一個類型對象,那麼我們在調用的時候就可以這樣來

C#

public List<TrainingUser>GetTrainingIdByUserId(string userId)        {              List<TrainingUser> trainingUserList =  DataTableHelper.ToList1<TrainingUser>(                    SqliteHelper.ExecuteDataset(System.Data.CommandType.Text,                        @"                        SELECT DISTINCT UserId,TrainingId FROM TRAININGUSER AS TU                        INNER JOIN [USER] AS U                         ON U.ID = TU.USERID                        JOIN [TRAINING] AS T                        ON T.ID = TU.TRAININGID                        WHERE U.ID = '"+ userId +"' AND T.ENDTIME > DATETIME('now', 'localtime') AND T.StartTime <= DATETIME('now', 'localtime') ;").Tables[0]);              return trainingUserList ;        }

代碼中的DataTableHelper.ToList1<TrainingUser> 即為我們剛才所寫的一個泛型方法,這樣我們可以看到,ToList<T> 傳入的類型為TrainingUser,同時接收者為:List<T> = List<TrainingUser> ,
這樣即便我們後續還有Training/User/Project等其他的類型,我們都可以直接使用DataTableHelper.ToList1<T>(DataTable dt) 來進行類型轉換.

2 : 提升程式的效能

泛型與非泛型相比較而言,效能要好一些,這是為什麼? 首先,泛型的類型是由調用者(接收者),去直接賦值的(型別安全的), 那麼就不會存在類型轉換的問題,其次, 泛型減少了裝箱和拆箱的過程.
執行個體 3 : 對於實值型別泛型與非泛型的效能比較

C#

private static void ListTest()        {            List<int>list = new List<int>();            for(inti = 0; i < 100; i++)            {                list.Add(i);                int a = list[i];            }            list =null;        }        private static void ArrListTest()        {            ArrayList arr = new ArrayList();            for(inti = 0; i <100; i++)            {                arr.Add(i);                int s = (int)arr[i];            }            arr = null;        }              Stopwatch sw = new Stopwatch();            sw.Start();            ListTest();            Console.WriteLine(" 使用泛型List執行實值型別方法曆時 :  "+ sw.Elapsed.ToString());            sw.Stop();             Stopwatch sw1 = new Stopwatch();            sw1.Start();            ArrListTest();            Console.WriteLine(" 使用非泛型ArrayList執行實值型別方法曆時 :  "+ sw1.Elapsed.ToString());            sw1.Stop();            Console.ReadLine();

通過迴圈 100 來比較,結果為 :

我們可以看到非泛型的時間要比泛型的時間多出0.0000523秒,泛型比非泛型的時間要多出一些, 那麼我們將數值改動一下改為迴圈 1000次.得出結果為 :

泛型比非泛型執行的時間要短0.0000405秒
我們將時間在改動一下,改為 100000呢?結果為 :

這次差距為 0.0054621 並且隨著執行次數的增長,非泛型相比泛型的時間會逐步的增加,
通過反編譯我們也能看出 :
泛型:

非泛型

從編譯中我們也能看出泛型方法中,接收的為Int32,非泛型為Object,其次泛型不會進行裝箱和拆箱操作,非泛型每次執行都要進行裝箱和拆箱操作.

3 : 型別安全

在執行個體1 , 2 ,3 中我們都有備忘說明,泛型的發送著必須要和接收者進行一致,否則會報異常 ,例如 :

執行個體 4 :

將一個泛型演算法應用於一個具體的類型時,編譯器和CLR能理解開發人員的意圖,並保證只有與指定資料類型相容的對象才能隨同演算法使用,若試圖使用不相容類型的一個對象,會造成編譯時間錯誤,或者運行時拋出異常



相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.