標籤:文法 ges 運行時 pac key employee 設計 多個 方法調用
這篇文章主要來講講c#中的泛型,因為泛型在c#中有很重要的位置,對於寫出高可讀性,高效能的代碼有著關鍵的作用。
一、什麼是泛型?
泛型是 2.0 版 C# 語言和公用語言運行庫 (CLR) 中的一個非常重要的新功能。
我們在編程程式時,經常會遇到功能非常相似的模組,只是它們處理的資料不一樣。但我們沒有辦法,只能分別寫多個方法來處理不同的資料類型。這個時候,那麼問題來了,有沒有一種辦法,用同一個方法來處理傳入不同種型別參數的辦法呢?泛型的出現就是專門來解決這個問題的,可以看出,微軟還是很貼心的。
二、為什麼要使用泛型?
接下來我們來看一段代碼。
public class GenericClass { public void ShowInt(int n) { Console.WriteLine("ShowInt print {0},ShowInt Parament Type Is {1}",n,n.GetType()); } public void ShowDateTime(DateTime dt) { Console.WriteLine("ShowDateTime print {0},ShowDateTime Parament Type Is {1}", dt, dt.GetType()); } public void ShowPeople(People people) { Console.WriteLine("ShowPeople print {0},ShowPeople Parament Type Is {1}", people, people.GetType()); } }View Code
static void Main(string[] args) { GenericClass generice = new GenericClass(); generice.ShowInt(11); generice.ShowDateTime(DateTime.Now); generice.ShowPeople(new People { Id = 11, Name = "Tom" }); Console.ReadKey(); }View Code
顯示結果:
我們可以看出這三個方法,除了傳入的參數不同外,其裡面實現的功能都是一樣的。在1.1版的時候,還沒有泛型這個概念,那麼怎麼辦呢。就有人想到了OOP三大特性之一的繼承,我們知道,C#語言中,所有類型都源自同一個類型,那就是object。
public class GenericClass { public void ShowObj(object obj) { Console.WriteLine("ShowObj print {0},ShowObj Parament Type Is {1}", obj, obj.GetType()); } } static void Main(string[] args) { Console.WriteLine("*****************object調用*********************"); generice.ShowObj(11); generice.ShowObj(DateTime.Now); generice.ShowObj(new People { Id = 11, Name = "Tom" }); Console.ReadKey(); }View Code
顯示結果:
我們可以看出,目地是達到了。解決了代碼的可讀性,但是這樣又有個不好的地方了,我們這樣做實際上是一個裝箱拆箱操作,會損耗效能。
終於,微軟在2.0的時候發布了泛型。接下來我們用泛型方法來實現該功能。
三、泛型型別參數
在使用泛型方法之前,我們先來瞭解下有關於泛型的一些知識。
在泛型型別或方法定義中,型別參數是在其執行個體化泛型型別的一個變數時,用戶端指定的特定類型的預留位置。 泛型類( GenericList<T>)無法按原樣使用,因為它不是真正的類型;它更像是類型的藍圖。 若要使用 GenericList<T>,用戶端代碼必須通過指定角括弧內的型別參數來聲明並執行個體化構造類型。 此特定類的型別參數可以是編譯器可識別的任何類型。 可建立任意數量的構造類型執行個體,其中每個使用不同的型別參數,如下所示:
GenericList<float> list1 = new GenericList<float>();GenericList<ExampleClass> list2 = new GenericList<ExampleClass>();GenericList<ExampleStruct> list3 = new GenericList<ExampleStruct>();
在 GenericList<T> 的每個執行個體中,類中出現的每個 T 在運行時均會被替換為型別參數。 通過這種替換,我們已通過使用單個類定義建立了三個單獨的型別安全的有效對象。
三、泛型約束
定義泛型類時,可以對用戶端代碼能夠在執行個體化類時用於型別參數的幾種類型施加限制。 如果用戶端代碼嘗試使用約束所不允許的類型來執行個體化類,則會產生編譯時間錯誤。 這些限制稱為約束。 通過使用 where 內容關鍵字指定約束。 下表列出了六種類型的約束:
where T:結構(型別參數必須是實值型別。可以指定除 Nullable 以外的任何實值型別。)
class MyClass<U> where U : struct///約束U參數必須為“值 類型” { } public void MyMetod<T>(T t) where T : struct { }
where T:類(型別參數必須是參考型別;這一點也適用於任何類、介面、委託或數群組類型。)
class MyClass<U> where U : class///約束U參數必須為“參考型別” { } public void MyMetod<T>(T t) where T : class { }
where T:new()(型別參數必須具有無參數的公用建構函式。當與其他約束一起使用時,new() 約束必須最後指定。)
class EmployeeList<T> where T : Employee, IEmployee, System.IComparable<T>, new(){ // ...}
where T:<基類名>(型別參數必須是指定的基類或派生自指定的基類。)
public class Employee{}public class GenericList<T> where T : Employee
where T:<介面名稱>(型別參數必須是指定的介面或實現指定的介面。可以指定多個介面約束。約束介面也可以是泛型的。)
/// <summary> /// 介面 /// </summary> interface IMyInterface { } /// <summary> /// 定義的一個字典類型 /// </summary> /// <typeparam name="TKey"></typeparam> /// <typeparam name="TVal"></typeparam> class Dictionary<TKey, TVal> where TKey : IComparable, IEnumerable where TVal : IMyInterface { public void Add(TKey key, TVal val) { } }
where T:U(為 T 提供的型別參數必須是為 U 提供的參數或派生自為 U 提供的參數。也就是說T和U的參數必須一樣)
class List<T>{ void Add<U>(List<U> items) where U : T {/*...*/}}
以上就是對六種泛型的簡單樣本,當然泛型約束不僅僅適用於類,介面,對於泛型方法,泛型委派都同樣適用。
三、泛型方法
public class GenericClass { public void ShowT<T>(T t) { Console.WriteLine("ShowT print {0},ShowT Parament Type Is {1}", t, t.GetType()); } }static void Main(string[] args) { Console.WriteLine("*****************泛型方法調用*********************"); generice.ShowT<int>(11); generice.ShowT<DateTime>(DateTime.Now); generice.ShowT<People>(new People { Id = 11, Name = "Tom" }); Console.ReadKey(); }View Code
顯示結果:
也是一樣的,現在終於實現了我們想要達到的效果了。我們可以看出,無論是什麼方式調用,最後我們擷取出來的類型都是原始類型。我們知道,用object擷取是利用了繼承這一特性,當編譯器編譯的時候,我們傳入的參數會進行裝箱操作,當我們擷取的時候又要進行拆箱操作,這個方法會損耗效能 。那麼泛型方法實現的原理又是怎樣的呢?首先,我們要知道,泛型是一個文法糖,在我們調用泛型方法,編譯器進行編譯時間,才會確定傳入的參數的類型,從而產生副本方法。這個副本方法與原始方法一法,所以不會有裝箱拆箱操作,也就沒有損耗效能這回事了。
四、泛型類
泛型類封裝不特定於特定資料類型的操作。
通常,建立泛型類是從現有具體類開始,然後每次逐個將類型更改為型別參數,直到泛化和可用性達到最佳平衡。
建立自己的泛型類時,需要考慮以下重要注意事項:
通常,可參數化類別型越多,代碼就越靈活、其可重用性就越高。 但過度泛化會造成其他開發人員難以閱讀或理解代碼。
其中一個有用的規則是,應用最大程度的約束,同時仍可處理必須處理的類型。 例如,如果知道泛型類僅用於參考型別,則請應用類約束。 這可防止將類意外用於實值型別,並 使你可在 T 上使用 as 運算子和檢查 null 值。
class BaseNode { }class BaseNodeGeneric<T> { }// concrete typeclass NodeConcrete<T> : BaseNode { }//closed constructed typeclass NodeClosed<T> : BaseNodeGeneric<int> { }//open constructed type class NodeOpen<T> : BaseNodeGeneric<T> { }
五、泛型介面
interface IMyGenericInterface<T>{}
interface IMyGenericInterface<TKey,TValue>{}
interface IBaseInterface<T> { }class SampleClass : IBaseInterface<string> { }//如果T有約束,那麼string類型必須得滿足T的約束
六、泛型委派
委託可以定義它自己的型別參數。 引用泛型委派的代碼可以指定型別參數以建立封閉式構造類型,就像執行個體化泛型類或調用泛型方法一樣,如以下樣本中所示:
class Program { static void Main(string[] args) { Del<int> m1 = new Del<int>(Notify); m1.Invoke(1111); Del<string> m2 = new Del<string>(Notify); m2.Invoke("字串"); Console.ReadKey(); } public delegate void Del<T>(T item); public static void Notify(int i) { Console.WriteLine("{0} type is {1}", i,i.GetType()); } public static void Notify(string str) { Console.WriteLine("{0} type is {1}", str, str.GetType()); } }
運行結果:
七、泛型代碼中的預設關鍵字:Default
在泛型類和泛型方法中產生的一個問題是,在預先未知以下情況時,如何將預設值分配給參數化型別 T:
T 是參考型別還是實值型別。
如果 T 為實值型別,則它是數值還是結構。
給定參數化型別 T 的一個變數 t,只有當 T 為參考型別時,語句 t = null 才有效;只有當 T 為數實值型別而不是結構時,語句 t = 0 才能正常使用。解決方案是使用 default 關鍵字,此關鍵字對於參考型別會返回空,對於數實值型別會返回零。對於結構,此關鍵字將返回初始化為零或空的每個結構成員,具體取決於這些結構是實值型別還是參考型別。
namespace MyGeneric{ class Program { static void Main(string[] args) { object obj1=GenericToDefault<string>(); object obj2 = GenericToDefault<int>(); object obj3 = GenericToDefault<StructDemo>(); Console.ReadKey(); } public static T GenericToDefault<T>() { return default(T); } } public struct StructDemo { public int Id { get; set; } public string Name { get; set; } }}
運行結果:
c#之泛型詳解