標籤:des style blog io color ar os 使用 sp
使用泛型的好處是“代碼重用”,極大的提高了開發效率,泛型為開發人員提供了以下優勢:
1,原始碼保護 演算法的原始碼不需要提供給使用泛型演算法的開發人員,使用c++模板的泛型技術需要提供。(目前c++模板的泛型技術瞭解較少)
2,型別安全 給泛型演算法指定類型時,編譯器能理解開發人員意圖,只有相容類型能通過,不相容的時候編譯時間候會報錯。
3,更清晰的代碼 由於編譯器強制類型的安全性,減少原始碼中必須進行的轉型次數,使得代碼容易維護和編寫。例如:DateTime dt=dtList[0];從DateTime的集合中按照索引取出來的值可以直接賦值給DateTime類型,不需要轉型。
4,更佳的效能 在操作實值型別時候,非泛型集合會造成裝箱、拆箱操作,會造成託管堆上的記憶體配置,會造成頻繁的記憶體回收,影響效能。
1.泛型基礎結構 1.1 開放類型和封閉類型
具有泛型型別參數的類型稱為開放類型。
不能建立執行個體。例如Dictionary<,>,沒有指定參數,目前尚不清楚這個開放類型有什麼用。
所有類型實參傳遞的都是實際資料類型為封閉類型。
使用約束無法將類型實參限制為某一類型,可以用一個靜態構造器來保證類型。如下
internal sealed class GenericTypeThatRequiresAnEnum<T>{ static GenericTypeThatRequiresAnEnum(){ if(!typeof(T).IsEnum){ throw new ArgumentException("T must be an enumerated type"); } }}
1.2泛型型別的繼承
泛型型別仍然是類型,它能從其他任何類型派生。
public class Node1<T> { public T m_data; public Node1<T> m_next; public Node1(T data) : this(data, null) { } public Node1(T data, Node1<T> next) { m_data = data; m_next = next; } public override string ToString() { // ABC return m_data.ToString()+((m_next!=null)?m_next.ToString():null); } }
上面例子必須是相同資料類型下使用,加入鏈表需要多個m_data為多種類型的時候這種結構將無法滿足,這時候我們可以考慮抽出一個非泛型的基類,這樣繼承的泛型就可以指定多種類型。這是一個泛型應用的技巧。
public class Node2 { protected Node2 m_next; public Node2(Node2 next) { m_next = next; } } public class TypeNode<T> : Node2 { public T m_data; public TypeNode(T data,Node2 next):base(next){ m_data = data; } public TypeNode(T data):this(data,null){ } public override string ToString() { // Tody is 時間。 return m_data.ToString() + ((m_next != null) ? m_next.ToString() : null); } }
1.3泛型型別的同一性
這種性質不經常用,這裡簡單記一下只當瞭解。
簡化泛型的寫法多封裝一層去除"<"">"。
class DateTimeList:List<DateTime>{
//這裡不需要放入任何代碼。
}
這樣使用的時候就沒有<,>符號了。
DateTimeList dt=new DateTimeList();
這隻是表面方便了,絕對不要單純出於增強代碼可讀性目的定義一個新類,事實上也不會這麼做,但是這樣寫會喪失同一性和相等性,如下代碼為Flase
Boolean sameType=(typeof(List<DateTime>)==typeof(DateTimeList));
可以通過使用using指令彌補相等性,添加如下結果為True;
using DateTimeList=System.Collections.Generic.List<System.DateTime>;
1.4代碼爆炸
使用泛型型別參數的一個方法在JIT(即時編譯)編譯時間,Clr擷取方法的IL,用指定的實參進行替換,建立恰當的本地代碼,缺點是CLR要為每種不同的方法、類型組合產生本地代碼,可能造成應用程式集顯著增大,損壞效能,稱之為 代碼爆炸。
但是CRL內建了一些最佳化措施,緩解代碼爆炸。所有程式集使用List<DateTime>時候,只會產生一次,認為所有參考型別實參都是完全相同,List<String>和List<Stream>可以公用,之所以會這樣,是因為所有參考型別的實參或者變數實際都是指向堆上的對象指標,而指標全部都是以相同的方式來操作。
2,泛型介面
泛型介面的一個例子是IComparable介面,在介面裡詳細寫
3,泛型委派
建議使用泛型的Action和Func委託,會在以後的委託中細說
4,委託和介面的逆變和協變泛型型別實參
不變數(invariant)表示泛型型別不可變。
逆變數(contravariant)表示泛型型別參數可以從一個基類更改為該類的衍生類別,用in關鍵字標記,只出現在輸入位置。
協變數(covariant) 表示泛型型別可以從一個衍生類別更改為它的基底類型,用out關鍵字標記,只出現在輸出位置。
public delegate TResult Func<in T,out TResult>(T arg);
Func<object,ArgumentException> fn1=null;
func<string,Exception> fn2=fn1;//這裡不需要顯示轉換,因為逆變數,協變數
調用委託Exception e=fn2("");
使用要擷取泛型參數和傳回值的委託時,建議盡量使用in和out關鍵字,因為不會有不良反應。
泛型介面和泛型委派一樣也可以用out和in。
5,泛型方法
用一個例子介紹下泛型的定義,下面一個類型定義了一個型別參數,一個方法定義了它自己的專用型別參數。
class GenericType<T>{ private T m_value; public GenericType(T value){m_value=value;} public TOutput Coverter(TOutput)(){ TOutput result=(TOutput)Convert.ChangeType(m_value,typeof(TOutput )); return result; } }
下面寫一個比較經典常用的泛型方法,2個參數互換
private static void Swap<T>(ref T o1,ref T o2){ T temp=o1; o1=o2; o2=temp; }
6泛型約束
確保使用當前泛型是自己想要的類型。
例如如下方法,在類型沒有提供CompareTo方法時候會報錯。
private static T Min<T>(T o1,To2){
if(o1.CompareTo(o2)<0)
return o1;
return o2;
}
這個時候我們就需要在該泛型方法添加泛型約束。
private static T Min<T>(T o1,To2) where T:IComparable<T>{
if(o1.CompareTo(o2)<0)
return o1;
return o2;
}
泛型約束主要分為3種。
1,主要約束 主要約束可以是一個參考型別,實參必須與約束相同或者派生,
例如where T:Stream ,使用該泛型方法必須是Stream 類型或者其衍生類別型。
where T:Class,使用該泛型方法必須是參考型別。
2,次要約束 次要約束代表的是一個借口類型,指定的參數必須實現所有介面約束例如 where T:IComparable<T>
3,構造器約束 指定的實參必須實現公用無參構造器的一個非抽象類別型where T:New()
下面是項目中用到的一個泛型方法,模板還原序列化。使用了Newtonsoft.Json
public T GetTemplateData<T>() where T : TemplateData { if (!string.IsNullOrEmpty(TemplateDataJsonStr)) { T obj = (T)JsonConvert.DeserializeObject(TemplateDataJsonStr, typeof(T)); obj.CheckField(); return obj; } else return null; } public void SetTemplateData(TemplateData templateData) { TemplateDataJsonStr= JsonConvert.SerializeObject(templateData); }
【Clr in c#】泛型