什麼是泛型
泛型(generic)特性提供了一種更優雅的方式,可以讓多個類型共享一組代碼。泛型允許我們聲明類型參數化(type-parameterized)的代碼,可以用不同的類型進行實例化。也就是說,我們可以用“類型佔位符”來寫代碼,然後在創建類的實例時指明真實的類型
C#中的泛型
- C#提供了5中泛型:類、結構、接口、委託和方法。前四中是類型,方法是成員
泛型類型是類型的模板
泛型和用戶自定義類型
泛型類
泛型類不是實際的類,而是類的模板,所以我們必須先從它們構建實際的類類型,然後創建這個構建後的類類型的實例
- 在某些類型上使用佔位符來聲明一個類
- 爲佔位符提供真實類型。這樣就有了真實類的定義,填補了所有的“空缺”。該類型稱爲構造類型(constructedtype)
- 創建構造類型的實例
從泛型類型創建實例
聲明泛型類
聲明一個簡單的泛型類和聲明普通類差不多
區別如下:
- 在類名之後放置一組尖括號
- 在尖括號中用逗號分隔的佔位符字符串來表示希望提供的類型。這叫做類型參數(typcparameter)
- 在泛型類聲明的主體中使用類型參數來表示應該替代的類型
// 類型參數
class SomeClass < T1, T2 >
{
// T1、T2表示在當前位置使用類型
public T1 SomeVar = new T1();
public T2 OtherVar = new T2();
}
創建構造類型
一旦創建了泛型類型,我們就需要告訴編譯器能使用哪些真實類型來替代佔位符(類型參數)。編譯器獲取這些真實類型並創建構造類型(用來創建真實類對象的模板)
創建構造類型的語法如下,包括列出類名並在尖括號中提供真實類型來替代類型參數。要替代類型參數的真實類型叫做類型實參(typeargument)
SomeClass < short, int >
類型參數和類型實參的區別(下圖)
創建變量和實例
MyNonGenClass myNGC = new MyNonGenClass ();
// 構造類 構造類
SomeClass < short, int > mySc1 = new SomeClass < short, int >();
var mySc2 = new SomeClass < short, int >();
ps: 可以從同一個泛型類構建出很多不同的類類型。每一個都有獨立的類類型,就好像它們都有獨立的非泛型聲明一樣
非泛型棧和泛型棧之間的區別
- | 非泛型 | 泛 型 |
---|---|---|
源代碼大小 | 更大:我們需要爲每一種類型編寫一個新的實現 | 更小:不管構造類型的數量有多少,我們只需要一個實現 |
可執行大小 | 無論每一個版本的棧是否會被使用,都會在編譯的版本中出現 | 可執行文件中只會出現有構造類型的類型 |
寫的難易度 | 易於書寫,因爲它更具體 | 比較難寫,因爲它更抽象 |
維護的難易度 | 更容易出問題,因爲所有修改需要應用到每一個可用的類型上 | 易於維護,因爲只需要修改一個地方 |
類型參數的約束
約束使用where字句列出
- 每一個有約束的類型參數有自己的where子句
- 如果形參有多個約束,它們在where子句中使用逗號分隔
where 參數類型 : 約束1, 約束2, ...
where 子句的要點:
- 它們在類型參數列表的關閉尖括號之後列出
- 它們不使用逗號或其他符號分隔
- 它們可以以任何次序列出
- where是上下文關鍵字,所以可以在其他上下文中使用
約束類型和次序
類名 | 只有這個類型的類或從它繼承的類才能用作類型實參 |
---|---|
class | 任何引用類型,包括類、數組、委託和接口都可以用作類型實參 |
struct | 任何值類型都可以用作類型實參 |
接口名 | 只有這個接口或實現這個接口的類型才能用作類型實參 |
new() | 任何帶有無參公共構造函數的類型都可以用作類型實參。這叫做構造函數約束 |
ps:where子句可以以任何次序列出。然而,where子句中的約束必須有特定的次序
- 最多只能有一個主約束,如果有則必須放在第一位
- 可以有任意多的接口名約束
- 如果存在構造函數約束,則必須放在最後
泛型方法
與其他泛型不一樣,方法是成員,不是類型。泛型方法可以在泛型和非泛型以及結構和接口中聲明
聲明泛型方法
泛型方法具有類型參數列表和可選的約束
- 泛型方法有兩個參數列表
- 封閉在圓括號內的方法參數列表
- 封閉在尖括號內的類型參數列表
- 要聲明泛型方法,需要:
- 在方法名稱之後 和方法參數之前放置類型參數列表
- 在方法參數列表後放置可選的約束子句
eg:
public void PrintData <S, T>(string str, int t) where S:Person
{
...
}
調用泛型方法
要調用泛型方法,應該在方法調用時提供類型實參
MyMethod <short,int>();
泛型方法的示例
class Simple // 非泛型類
{
static public void ReverseAndPrint<T>(T[] arr) // 泛型方法
{
Array.Reverse(arr);
foreach (T iten in arr) // 使用類型參T
Console.krite("(0),", item.ToString());
Console.Mriteline(*);
}
}
class Program
{
static void Kain()
{
//創建各種類型的數糕
var intArray … new int[] {3, 5, 7, 9, 11 };
var stringArray - new string[] { "first", "second", "third" };
var doubleArray * nex double[]{ 3.567, 7.891, 2.345 };
Simple.ReverseAndPrintcint>(intArray); // 調用方法
Simple.ReverseAndPrint(intArray); // 推斷類型並調用
Simple.ReverseAndPrint<string>(stringArray); // 調用方法
Simple.ReverseAndPrint(stringArray); // 推斷類型並調用
Simple.ReverseAndPrintcdouble>(doubleArray); // 調用方法
Simple.ReverseAndPrint(doubleArray); // 推斷類型並調用
}
}
上述案例的結果:
11, 9, 7, 5, 3
3, 5, 7, 9, 11
third, second, first
first, second, third
2.345, 7.891, 3.567
3.567, 7.891, 2.345
擴展方法和泛型類
泛型類的擴展方法:
- 必須聲明爲static
- 必須是靜態類的成員
- 第一個參數類型中必須有關鍵字this,後面是擴展的泛型類的名字
案例
static class ExtendHolder
{
public static void Print<T>(this Holder<T> h)
{
T[] vals= h.GetValues();
Console.NriteLine("{0},\t{1},\t{2}",vals[0], vals[1],vals[2]);
}
}
class Holder<T>
{
T[] Vals = new T[3];
public Holder(T V0, T V1,T v2)
{
vals[0] = v0;
Vals[1] = v1;
Vals[2] = v2;
}
public T[] GetValues()
{
return Vals;
}
class Program
{
static void Main(string[] args)
{
var intHolder = new Holder<int>(3, 5, 7);
var stringHolder = new Holder<string>("a1", "b2", "c3");
intHolder.Print();
stringHolder.Print();
}
}
上述案例的結果:
3, 5, 7
a1, b2, c3
泛型結構
- 與泛型類相似,範型結構可以有類型參數和約束
- 範型結構的規則和條件與泛型類是一樣的
案例
struct PieceOfData<T> //泛型結構
{
public PieceOfData(T value)
{
_data = value;
}
private T _data;
public T Data
{
set _data = value;
{
}
class Program
{
static void Main()
{
// 構造類型
var intData = new Piece0fData<int>(10);
// 構造類型
var stringData = new PieceOfData<string>("Hi there.");
Console.WriteLine("intData = {0}", intData.Data);
Console.WriteLine("stringData = {0}", stringData.Data);
}
}
案例結果:
intData = 10
stringData = Hi there
泛型委託
- 泛型委託和非泛型委託非常相似,不過類型參數決定了能接受什麼樣的方法
- 要聲明泛型委託,在委託名稱後、委託參數列表之前的尖括號中放置類型參數列表
案例
delegate void MyDelegate<T>(T value); // 泛型委託
class Simple
{
static public void PrintString(string s) // 方法匹配委託
{
Console.WriteLine(s);
}
static public void PrintUpperString(string s) // 方法匹配委託
{
Console.MriteLine("(O)",s.ToUpper());
}
}
class Program
{
static void Main()
{
//創建委託的實例
var myDel = new MyDelegate<string>(Simple.PrintString);
//添加方法
myDel += Simple.PrintUpperString;
//調用委託
myDel("Hi There.");
}
}
案例結果:
Hi There
Hi There
泛型接口
泛型接口允許我們編寫參數和接口成員返回類型是泛型類型參數的接口。泛型接口的聲明和非泛型接口的聲明差不多,但是需要在接口名稱之後的尖括號中放置類型參數
案例
interface IMyIfc<T> // 泛型接口
{
T RetrnIt( T inValue);
}
// 類型參數 泛型接口
class Simple<S> :IMyIfc<S> // 泛型類
{
public S ReturnIt(S inValue) //實現泛型接口
{
return inValue
}
}
class Program
{
static void Main()
{
var trivInt = new Simple<int>();
var trivString = new Simple<string>();
Console.WriteLine("{0)",trivInt.ReturnIt(5));
Console.NriteLine("{o)",trivString.ReturnIt("Hi there."));
}
}
案例結果:
5
Hi there.
ps:泛型接口的實現必須唯一