泛型
泛型是什么丐一?
即通過(guò)參數(shù)化類型來(lái)實(shí)現(xiàn)在同一份代碼上操作多種數(shù)據(jù)類型藻糖。
泛型可以定義類型安全類,而不會(huì)損害類型安全库车、性能或工作效率巨柒。
泛型怎么寫(xiě)?
泛型類
class MyClass<T>
{
//.............
}
泛型方法
public void MyMethod<T>()
{
//.............
}
泛型接口
interface MyInterface<T>
{
//.............
}
泛型結(jié)構(gòu)
struct MyStruct<T>
{
//.............
}
相對(duì)于C#中的類的聲明等語(yǔ)法柠衍,其實(shí)泛型只是多加了一個(gè)類型參數(shù)用來(lái)表示不同類型而已洋满。添加的方式也就是在類、方法等名稱后面加 <T>珍坊。
泛型的使用
對(duì)于泛型的使用牺勾,總結(jié)成一句話就是,跟普通類的使用是幾乎一樣的阵漏,只是在使用過(guò)的時(shí)候需要將類型參數(shù)確定為明確的類型驻民。
泛型就是一個(gè) <T> ,在使用時(shí)將 T 換成確定的類型履怯。
泛型的優(yōu)點(diǎn)
性能
泛型 的一個(gè)優(yōu)點(diǎn)是性能回还。
對(duì)值類型使用 非泛型集合類 在把值類型轉(zhuǎn)換為引用類型,和把引用類型轉(zhuǎn)換為值類型時(shí)需要進(jìn)行裝箱和拆箱操作叹洲。
裝箱拆箱鏈接柠硕。
值存儲(chǔ)在棧上,引用存儲(chǔ)在堆上疹味。
泛型在使用時(shí)定義具體的類型仅叫,避免裝箱拆箱操作帜篇。
類型安全
泛型類型需要在使用時(shí)指定糙捺,只能使用泛型指定的類型,編譯器會(huì)在編譯前檢測(cè)笙隙,提前發(fā)現(xiàn)錯(cuò)誤洪灯。
二級(jí)制代碼重用
泛型可以更好的重用二級(jí)制代碼。泛型類可以定義一次竟痰,并且享有許多不同的類型實(shí)例化签钩。
代碼擴(kuò)展
泛型類的定義會(huì)放在程序集中掏呼,所以使用特定類型實(shí)例化泛型類不會(huì)在IL代碼中重復(fù)這些類。
編譯器將泛型類編譯為本地代碼后會(huì)給每一個(gè)值創(chuàng)建一個(gè)新類铅檩,引用類型共享一個(gè)本地類的所有實(shí)現(xiàn)代碼憎夷。
因?yàn)橐妙愋偷姆盒皖悓?shí)例化只需要4個(gè)字節(jié)內(nèi)存地址,就可以引用一個(gè)引用類型昧旨。
而對(duì)于值類型的存在于泛型類實(shí)例化內(nèi)存中拾给,每個(gè)值類型對(duì)內(nèi)存的要求不同,所以要為每一個(gè)類創(chuàng)建一個(gè)新類兔沃。
引用類型大小確定蒋得,有限,所以引用類型共享代碼乒疏。值類型大小不固定额衙,每個(gè)值類型的實(shí)例化都會(huì)創(chuàng)建新類。
命名約定
- 泛型類型使用字母T作為前綴怕吴。
- 如果沒(méi)有特殊要求窍侧,泛型類型允許使用任意類型代替,且只使用了一個(gè)泛型類型械哟,就可以用字符T作為泛型類型的名稱疏之。
- 如果泛型類型有特定的要求,或者使用了兩個(gè)或多個(gè)泛型類型暇咆,就應(yīng)該給泛型類型使用描述性名稱锋爪。
using System;
using static System.Console;
namespace ConsoleApp22
{
class Program
{
public class MyClass<T1,T2>
{
public T1 X1;
public T2 X2;
}
static void Main(string[] args)
{
MyClass<int,string> s = new MyClass<int,string>();
s.X1 = 100;
s.X2 = "ASD";
WriteLine(s.X1);
WriteLine(s.X2);
ReadKey();
}
}
}
描述性名稱并沒(méi)有具體含義,但是要盡量表明其期望的含義爸业,正常應(yīng)該寫(xiě)為Tint,Tstring
泛型(Generic) 允許您延遲編寫(xiě)類或方法中的編程元素的數(shù)據(jù)類型的規(guī)范其骄,直到實(shí)際在程序中使用它的時(shí)候。換句話說(shuō)扯旷,泛型允許編寫(xiě)一個(gè)可以與任何數(shù)據(jù)類型一起工作的類或方法拯爽。
可以通過(guò)數(shù)據(jù)類型的替代參數(shù)編寫(xiě)類或方法的規(guī)范。當(dāng)編譯器遇到類的構(gòu)造函數(shù)或方法的函數(shù)調(diào)用時(shí)钧忽,它會(huì)生成代碼來(lái)處理指定的數(shù)據(jù)類型毯炮。
泛型 可以創(chuàng)建獨(dú)立于被包含類型的類和方法,我們不必給不同的類型編寫(xiě)許多相同的方法或類耸黑,只創(chuàng)建一個(gè)方法或類即可桃煎。
另一個(gè)減少代碼的選項(xiàng)是使用object類,但使用派生自object類的類型進(jìn)行傳遞不是類型安全的大刊。
泛型類 使用 泛型類型 为迈,并且可以根據(jù)特定的類型替換泛型類型,保證了安全性。
如果這個(gè) 泛型 不支持 泛型類 葫辐,編譯器就會(huì)出現(xiàn)錯(cuò)誤搜锰。
泛型 不局限于類,還有用于接口和方法的 泛型耿战。以及用于委托的泛型
委托的泛型鏈接
泛型類
創(chuàng)建泛型類
首先介紹一個(gè)一般的蛋叼,非泛型的簡(jiǎn)化鏈表類,它可以包含任意多的對(duì)象剂陡,以后再把這個(gè)類轉(zhuǎn)化為泛型類鸦列。
在鏈表中,一個(gè)元素引用下一個(gè)元素鹏倘。
先創(chuàng)建一個(gè)類薯嗤,這部分可以直接看C#實(shí)現(xiàn)鏈表
類中包含鏈表需要的元素。
public class LinkedListNode
{
public LinkedListNode(object value)
{
Value = value;
}
public object Value { get; set; }//值
public LinkedListNode Next { get; internal set; }//指向下一個(gè)
public LinkedListNode Prev { get; internal set; }//指向上一個(gè)
}
然后實(shí)現(xiàn)具體的鏈表纤泵。
public class LinkedList : IEnumerable//IEnumerable 是 .NET 的接口骆姐,用于實(shí)現(xiàn)迭代
{
public LinkedListNode First { get; set; }//鏈表頭
public LinkedListNode Last { get; set; }//鏈表尾
public LinkedListNode AddLast(object node)//創(chuàng)建鏈表
{
var newNode = new LinkedListNode(node);//對(duì)鏈表數(shù)量化
if (First == null)//如果頭為空
{
First = newNode;//讓鏈表頭為新鏈表
Last = First;//鏈表頭和鏈表尾相等
}
else//如果鏈表頭部位空
{
Last.Next = newNode;//尾鏈表的下一個(gè)元素為新鏈表
Last = newNode;//尾鏈表尾新鏈表
}
return newNode;//返回鏈表
}
public IEnumerator GetEnumerator()//迭代方法獲取鏈表
{
LinkedListNode current = First;//獲取鏈表頭
while (current != null)//當(dāng)前鏈表不為空
{
yield return current.Value;//迭代返回鏈表的下一個(gè)元素
current = current.Next;//當(dāng)前元素為下一個(gè)元素
}
}
實(shí)現(xiàn)鏈表頭尾,擴(kuò)展鏈表捏题,鏈表遍歷通過(guò) 枚舉器 詳情見(jiàn) c#數(shù)組和元組
然后就可以對(duì)鏈表進(jìn)行操作玻褪,
var list1 = new LinkedList();
list1.AddLast(2);
list1.AddLast(4.78);
list1.AddLast("6");
foreach (var i in list1)
{
WriteLine(i);
}
接下來(lái)我們創(chuàng)建泛型類版本的鏈表。
using System;
using System.Collections;
using System.Collections.Generic;
using static System.Console;
namespace ConsoleApp22
{
class Program
{
public class LinkedListNode<T>//定義一個(gè)鏈表泛型類
{
public LinkedListNode(T value)//泛型類的構(gòu)造函數(shù)
{
Value = value;
}
public T Value { get; }//只讀字段鏈表值
public LinkedListNode<T> Next { get; internal set; }//指向下一個(gè)元素指針
public LinkedListNode<T> Prev { get; internal set; }//指向上一個(gè)元素指針
}
public class LinkedList<T> : IEnumerable<T>//鏈表具體實(shí)現(xiàn)
{
public LinkedListNode<T> First { get; private set; }//鏈表類型的鏈表頭
public LinkedListNode<T> Last { get; private set; }//鏈表類型的鏈表尾
public LinkedListNode<T> AddLast(T node)//鏈表方法公荧,返回一個(gè)鏈表
{
var newNode = new LinkedListNode<T>(node);//對(duì)鏈表進(jìn)行實(shí)例化
if (First == null)//通過(guò)頭結(jié)點(diǎn)為空
{
First = newNode;//頭結(jié)點(diǎn)為鏈表
Last = First;//尾結(jié)點(diǎn)就是頭結(jié)點(diǎn)
}
else//如果頭結(jié)點(diǎn)不為空
{
Last.Next = newNode;//尾結(jié)點(diǎn)的下一個(gè)結(jié)點(diǎn)為新傳入的結(jié)點(diǎn)
Last = newNode;//尾結(jié)點(diǎn)為這個(gè)新鏈表
}
return newNode;//返回新列表
}
public IEnumerator<T> GetEnumerator()//對(duì)鏈表遍歷方法带射,返回一個(gè)可迭代泛型集合
{
LinkedListNode<T> current = First;//當(dāng)前指針獲取頭結(jié)點(diǎn)
while (current != null)//如果結(jié)點(diǎn)存在
{
yield return current.Value;//迭代返回當(dāng)前結(jié)點(diǎn)的值
current = current.Next;//當(dāng)前結(jié)點(diǎn)轉(zhuǎn)向下一個(gè)結(jié)點(diǎn)
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
static void Main()
{
var list2 = new LinkedList<int>();//對(duì)泛型實(shí)例化,規(guī)定鏈表只能傳入int
list2.AddLast(1);//撰寫(xiě)鏈表
list2.AddLast(3);
list2.AddLast(5);
foreach (int i in list2)//遍歷鏈表的值
{
WriteLine(i);
}
var list3 = new LinkedList<string>();//只能傳入字符串
list3.AddLast("2");
list3.AddLast("four");
list3.AddLast("foo");
foreach (string s in list3)
{
WriteLine(s);
}
ReadKey();
}
}
}
泛型類的定義與一般類類似循狰,只是要使用泛型聲明窟社。
之后,泛型類型就可以在類中用作一個(gè)字段成員绪钥,或者方法的參數(shù)類型灿里,
public class LinkedListNode<T>//定義一個(gè)鏈表泛型類
{
public T Value;
}
構(gòu)造函數(shù)也可以變?yōu)榻邮躎類型的對(duì)象,也可以返回和設(shè)置泛型類型程腹。
public LinkedListNode(T value)//泛型類的構(gòu)造函數(shù)
{
Value = value;
}
泛型類的功能
默認(rèn)值
不能將null賦予泛型類型匣吊,因?yàn)榉盒涂梢詫?shí)例化為值類型,而null只能用于引用類型寸潦。
可以使用 default 關(guān)鍵字將 null在示例化為值類型時(shí)變成0.
class Test<T>
{
public T F()
{
T t = null; //有問(wèn)題
return t;
}
}
t在這里無(wú)法編譯色鸳,無(wú)法將null轉(zhuǎn)換為參數(shù)類型T,因?yàn)樗赡苁遣豢梢詾閚ull的值類型见转。
所以需要使用 default
class Test<T>
{
public T F()
{
return default(T);
}
}
default對(duì)應(yīng)各種類型生成默認(rèn)值列表如下
類型 | 默認(rèn)值 |
---|---|
任何引用類型 | null |
數(shù)值類型 | 0 |
bool | false |
enum | 表達(dá)式(E)0生成的值命雀,其中E是枚舉標(biāo)識(shí)符 |
struct | 將所有值類型設(shè)為其默認(rèn)值,將所有引用類型設(shè)為null |
可以為null的類型 | HasValue屬性為false池户,且Value屬性未定義的實(shí)例 |
約束
如果泛型類需要調(diào)用泛型類型的方法咏雌,就必須添加約束
泛型接受的約束類型
約束 | 說(shuō)明 |
---|---|
where T : sturct | 對(duì)于結(jié)構(gòu)約束,泛型必須是值類型 |
where T : class | 類約束指定類型T必須是引用類型 |
where T : IFoo | 指定類型T必須實(shí)現(xiàn)接口IFoo |
where T : Foo | 指定類型T必須派生自基類Foo |
where T : new() | 這個(gè)是一個(gè)構(gòu)造函數(shù)約束校焦,指定類型T必須有一個(gè)默認(rèn)構(gòu)造函數(shù) |
where T1: T2 | 這個(gè)約束也可以指定赊抖,類型T1派生自泛型類型T2 |
**只能為默認(rèn)構(gòu)造函數(shù)定義構(gòu)造函數(shù)約束,不能為其他構(gòu)造函數(shù)定義構(gòu)造函數(shù)約束寨典。
使用泛型類型還可以合并多個(gè)約束氛雪。
class MyClassy<T1,T2>
where T1: struct
where T2: class耸成,new()
{
//....
}
指定T1必須是值類型,T2必須是引用類型且必須有一個(gè)默認(rèn)構(gòu)造函數(shù)报亩。
**在C#中,where子句的一個(gè)重要限制是不能定義必須由泛型類型實(shí)現(xiàn)運(yùn)算符井氢,運(yùn)算符不能在接口定義 弦追。在where子句中,只能定義基類花竞,接口和默認(rèn)構(gòu)造函數(shù)劲件,
繼承
泛型類可以實(shí)現(xiàn)泛型接口,也可以派生自一個(gè)類约急,泛型類可以派生自泛型基類零远。
public class MyClass1<T>
{
//.......
}
public class MyClass2<T> : MyClass1<T>
{
//.......
}
派生類不必是泛型類
public class MyClass1<T>
{
//.......
}
public class MyClass2<T> : MyClass1<string>
{
//.......
}
泛型接口同樣可以被繼承
interface IMethod<T>
{
void f();
}
public class MyClass3 : IMethod<int>
{
public void f()
{
WriteLine(777);
}
}
靜態(tài)成員
泛型類的靜態(tài)成員需要特別關(guān)注,泛型類型的靜態(tài)成員只能在類中的一個(gè)實(shí)例中共享厌蔽,
using System;
using static System.Console;
namespace ConsoleApp23
{
class Program
{
public class MyClass<T>
{
public static T x;
}
static void Main(string[] args)
{
MyClass<int>.x = 100;
MyClass<string>.x = "asdw";
ReadKey();
}
}
}
string牵辣,int是兩個(gè)不同的類型,這樣就出現(xiàn)兩個(gè)不同的靜態(tài)字段x奴饮。
泛型接口
使用泛型可以定義接口纬向,在接口中定義的方法可以帶泛型參數(shù),
泛型接口的協(xié)變和抗變
參數(shù)類型是協(xié)變的戴卜。
泛型參數(shù)定義的類型只能作為方法的返回類型罢猪,不能作為方法的參數(shù)類型,且該類型直接或者間接地繼承自接口方法的返回值類型;可以使用out關(guān)鍵字聲明協(xié)變參數(shù)
如果泛型類型使用 out 關(guān)鍵字做標(biāo)注叉瘩,泛型接口就是協(xié)變的膳帕,這也意味著返回類型只能是T。
如:string->object (子類到父類的轉(zhuǎn)換)
返回的方法是抗變的薇缅。
泛型參數(shù)定義的類型只能作為方法參數(shù)的類型危彩,不能作為返回值類型,且該類型是接口方法的參數(shù)類型的基類型泳桦;可以使用in關(guān)鍵字聲明抗變參數(shù)汤徽。
如果泛型類型使用 in 關(guān)鍵字做標(biāo)注,泛型接口就是抗變的灸撰,這也意味接口只能將類型T用作其方法的輸入谒府。
如:object->string (父類到子類的轉(zhuǎn)換)
泛型結(jié)構(gòu)
與類類似拼坎,結(jié)構(gòu)也可以是泛型的,非常類似于泛型類完疫,但是沒(méi)有繼承泰鸡。
using System;
using static System.Console;
namespace ConsoleApp23
{
class Program
{
public struct MyStruct<T>
{
public T A;
public T B;
}
static void Main(string[] args)
{
MyStruct<int> s;
int a = s.A = 100;
int b = s.B = 777;
WriteLine(a);
WriteLine(b);
ReadKey();
}
}
}
泛型方法
在泛型方法中,泛型類型用方法聲明來(lái)定義壳鹤。
泛型方法可以在非泛型類中定義盛龄。
泛型方法示例
using System;
using static System.Console;
namespace ConsoleApp23
{
class Program
{
public class MyClass
{
public static void swap<T>(ref T a,ref T b)
{
T temp;
temp = a;
a = b;
b = temp;
}
}
static void Main(string[] args)
{
int a = 4;
int b = 10;
WriteLine("{0} {1}",a,b);
MyClass.swap<int>(ref a, ref b);
WriteLine("{0} {1}",a,b);
ReadKey();
}
}
}
這里為什么要用ref。ref關(guān)鍵字用于將方法內(nèi)的變量改變后帶出方法外芳誓。
帶約束的泛型方法
泛型類可以用where子句限制余舶,泛型方法也可以用where子句限制,
public static void swap<T>(ref T a,ref T b) where T: struct
{
T temp;
temp = a;
a = b;
b = temp;
}
帶委托的泛型方法
http://www.reibang.com/p/5a12f3f74c62
泛型方法規(guī)范
泛型方法可以重載锹淌,為特定類型定義規(guī)范匿值,這也適用于帶泛型的方法。
public void F<T>(T x)
{
WriteLine(x);
}
public void F(int x)
{
WriteLine(x);
}
public void F<T1,T2>( T1 x1,T2 x2)
{
WriteLine("{0} {1}",x1,x2);
}
public void F(int x1,int x2)
{
WriteLine("{0} {1}", x1, x2);
}
上面完成F方法的四次重載赂摆。
在編譯期間千扔,編譯器會(huì)使用最佳匹配。
泛型腦涂概覽
腦圖轉(zhuǎn)載自 牧白 # C#-弄懂泛型和協(xié)變库正、逆變