這篇文章主要來講講c#中的泛型,因爲泛型在c#中有很重要的位置,對於寫出高可讀性,高性能的代碼有着關鍵的作用。
一、什麼是泛型?
泛型是 2.0 版 C# 語言和公共語言運行庫 (CLR) 中的一個非常重要的新功能。
我們在編程程序時,經常會遇到功能非常相似的模塊,只是它們處理的數據不一樣。但我們沒有辦法,只能分別寫多個方法來處理不同的數據類型。這個時候,那麼問題來了,有沒有一種辦法,用同一個方法來處理傳入不同種類型參數的辦法呢?泛型的出現就是專門來解決這個問題的,可以看出,微軟還是很貼心的。
二、使用泛型的好處
1、未使用泛型的代碼
非泛型結構的方法,在面對參數類型不同時,需要提供不同的方法,使得代碼冗餘率高,影響開發效率
public class CommonMethod
{
public static void ShowInt(int iParameter)
{
Console.WriteLine("This is {0},parameter={1},type={2}",
typeof(CommonMethod).Name, iParameter.GetType().Name, iParameter);
}
public static void ShowString(string sParameter)
{
Console.WriteLine("This is {0},parameter={1},type={2}",
typeof(CommonMethod).Name, sParameter.GetType().Name, sParameter);
}
public static void ShowDateTime(DateTime dtParameter)
{
Console.WriteLine("This is {0},parameter={1},type={2}",
typeof(CommonMethod).Name, dtParameter.GetType().Name, dtParameter);
}
}
static void Main(string[] args)
{
CommonMethod.ShowInt(18);
CommonMethod.ShowString("hello world!");
CommonMethod.ShowDateTime(DateTime.Now);
}
2、使用泛型的代碼
泛型方法解決了用一個方法,滿足不同參數類型,做同樣的事情。不寫死參數類型,調用的時候才指定參數的類型,由此達到延遲聲明的目的。
public class GenericMethod
{
/// <summary>
/// 泛型方法
/// </summary>
/// <typeparam name="T">T 不要用關鍵字 也不要跟別的類型衝突 </typeparam>
/// <param name="tParameter"></param>
public static void Show<T>(T tParam)
{
Console.WriteLine("This is {0},parameter={1},type={2}",
typeof(GenericMethod).Name, tParam.GetType().Name, tParam.ToString());
}
}
static void Main(string[] args)
{
GenericMethod.Show<int>(18);
GenericMethod.Show<string>("hello world!");
GenericMethod.Show<DateTime>(DateTime.Now);
}
三、泛型的使用範圍
1、泛型方法
public class GenericMethod
{
public static void Show<T>(T tParameter)
{
Console.WriteLine("This is {0},parameter={1},type={2}",
typeof(GenericMethod).Name, tParameter.GetType().Name, tParameter.ToString());
}
}
static void Main(string[] args)
{
GenericMethod.Show<int>(18);
GenericMethod.Show<string>("hello world!");
GenericMethod.Show<DateTime>(DateTime.Now);
}
泛型方法在被調用之後才確定參數的類型,我們在調用同一個方法,最後我們獲取到的參數類型都是原始類型,在我們調用泛型方法,編譯器進行編譯時,纔會確定傳入參數的類型,從而生成副本方法。這個方法與原始方法一樣,所以不會有裝箱和拆箱的操作。
2、泛型類
class CommonNode { }
class GenericClass<T> { }
class GenericComm<T> : CommonNode { }
class GenericNode<T> : GenericClass<int> { }
class GenericOpen<T> : GenericNode<T> { }
3、泛型接口
定義一個泛型接口:
interface IGenericInterface<T>
{
}
一個接口可定義多個類型參數:
interface IGenericInterface<TKey,TValue>
{
}
具體類可實現封閉式構造接口:
interface IBaseInterface<T> { }
class GenericClass : IBaseInterface<string> { }
4、泛型委託
委託的方法可以定義不同類型的參數。 引用泛型委託的代碼可以指定類型參數以創建封閉式構造類型,就像實例化泛型類或調用泛型方法一樣。如示例所示:
class Program
{
static void Main(string[] args)
{
SayHi<int> hi1 = new SayHi<int>(Show);
hi1.Invoke(123);
SayHi<string> hi2 = new SayHi<string>(Show);
hi2.Invoke("hello");
}
public delegate void SayHi<T>(T t);//泛型委託
public static void Show(int i) { Console.WriteLine("{0} type is {1}", i,i.GetType()); }
public static void Show(string str) { Console.WriteLine("{0} type is {1}", str, str.GetType()); }
}
5、泛型約束
約束類型 | 詳解 |
---|---|
T:結構 | 類型參數必須是值類型。可以指定除 Nullable 以外的任何值類型。有關更多信息,請參見使用可空類型(C# 編程指南)。 |
T:類 | 類型參數必須是引用類型,包括任何類、接口、委託或數組類型。 |
T:new() | 類型參數必須具有無參數的公共構造函數。當與其他約束一起使用時,new() 約束必須最後指定。 |
T:<基類名> | 類型參數必須是指定的基類或派生自指定的基類。 |
T:<接口名稱> | 類型參數必須是指定的接口或實現指定的接口。可以指定多個接口約束。約束接口也可以是泛型的。 |
T:U | 爲 T 提供的類型參數必須是爲 U 提供的參數或派生自爲 U 提供的參數。這稱爲裸類型約束。 |
- where T:結構
類型參數必須爲值類型,可以指定除 Nullable 以外的任何值類型。
class MyClass<U>
where U : struct///約束U參數必須爲“值 類型”
{ }
public void MyMetod<T>(T t)
where T : struct
{ }
- where T:類
指出某個類型必須將指定的類作爲基類(或者就是該類本身),才能用作該泛型類型的類型參數。這樣的約束一經使用,就必須出現在該類型參數的所有其他約束之前。
class MyClassy<T, U>
where T : class
where U : class
{ }
- where T:new()
以使用 new 運算符創建類型參數的實例;但類型參數爲此必須受構造函數約束 new() 的約束。new() 約束可以讓編譯器知道:提供的任何類型參數都必須具有可訪問的無參數(或默認)構造函數。new() 約束出現在 where 子句的最後。
public class MyGenericClass <T>
where T: IComparable
, new()
{
// The following line is not possible without new() constraint:
T item = new T();
}
- where T:<基類名>
指出某個類型必須將指定的類作爲基類(或者就是該類本身),才能用作該泛型類型的類型參數。這樣的約束一經使用,就必須出現在該類型參數的所有其他約束之前。
class MyClassy<T, U>
where T : class
where U : struct
{ }
- where T:<接口名稱>
例如,可以聲明一個泛型類 MyGenericClass,這樣,類型參數 T 就可以實現 IComparable 接口:
public class MyGenericClass<T>
where T:IComparable
{ }
- where T:U
用作約束的泛型類型參數稱爲裸類型約束。當具有自己的類型參數的成員函數需要將該參數約束爲包含類型的類型參數時,裸類型約束很有用。
class List<T>
{
void Add<U>(List<U> items) where U : T {/*...*/}
}
泛型類的裸類型約束的作用非常有限,因爲編譯器除了假設某個裸類型約束派生自 System.Object 以外,不會做其他任何假設。在希望強制兩個類型參數之間的繼承關係的情況下,可對泛型類使用裸類型約束。
6、default關鍵字
之所以會用到default關鍵字,是因爲需要在不知道類型參數爲值類型還是引用類型的情況下,爲對象實例賦初值。考慮以下代碼:
class TestDefault<T>
{
public T foo()
{
T t = null; //???
return t;
}
}
如果我們用int型來綁定泛型參數,那麼T就是int型,那麼註釋的那一行就變成了 int t = null;顯然這是無意義的。爲了解決這一問題,引入了default關鍵字:
class TestDefault<T>
{
public T foo()
{
return default(T);
}
}