C# 泛型(Generics)
泛型概述
泛型是C#編程語言的一部分,它與程序集中的IL
(Intermediate Language
差牛,中間語言)代碼緊密的集成券盅。通過泛型,我們不必給不同的類型編寫功能相同的許多方法和類司顿,而是可以創(chuàng)建獨(dú)立于被包含類型的一個(gè)類或方法。 例如兄纺,通過使用泛型類型參數(shù) T大溜,可以編寫其他客戶端代碼能夠使用的單個(gè)類,而不會產(chǎn)生運(yùn)行時(shí)轉(zhuǎn)換或裝箱操作的成本或風(fēng)險(xiǎn)估脆。使用泛型類型可以最大限度地重用代碼钦奋、保護(hù)類型安全性以及提高性能。
泛型性能
泛型的一個(gè)主要優(yōu)點(diǎn)是性能疙赠。值類型存儲在棧上付材,引用類型存儲在堆上。從值類型轉(zhuǎn)換為引用類型稱為裝箱圃阳;從引用類型轉(zhuǎn)換為值類型稱為拆箱厌衔。對值類型使用非泛型集合類,常常需要將值類型和引用類型互相轉(zhuǎn)換捍岳,進(jìn)行裝箱和拆箱操作富寿,性能損失比較大。而使用了泛型锣夹,可以很好的解決這一問題页徐,泛型可以不再進(jìn)行裝箱和拆箱操作。
泛型類型安全
泛型的另一個(gè)特性是類型安全银萍。例如变勇,在泛型類List<T>
中,泛型類型T
定義了允許使用的類型贴唇。假設(shè)有一個(gè)泛型實(shí)例為List<int>
搀绣,它在添加元素時(shí)赃梧,就只會添加類型為int
的數(shù)值到集合中。
泛型允許二進(jìn)制代碼重用
泛型允許更好的重用二進(jìn)制代碼豌熄,泛型類可以定義一次,使用許多不同的類型實(shí)例化物咳。例如锣险,泛型類List<T>
可以實(shí)例化為List<int>
、List<string>
览闰、List<MyClass>
等芯肤。
泛型實(shí)例化時(shí)代碼生成
泛型類的定義會放在程序集 中,所以用特定類型實(shí)例化泛型類不會在中間語言(IL)代碼中復(fù)制這些類压鉴。但是崖咨,在JIT編譯器把泛型類編譯為本地代碼時(shí),會給每個(gè)值類型創(chuàng)建一個(gè)新類油吭。而引用類型共享同一個(gè)本地類的所有相同的實(shí)現(xiàn)代碼击蹲。這是因?yàn)橐妙愋驮趯?shí)例化泛型類中只需要4個(gè)字節(jié)的內(nèi)存地址(32位系統(tǒng)),就可以引用一個(gè)引用類型婉宰。值類型包含在實(shí)例化的泛型類的內(nèi)存中歌豺,同時(shí)因?yàn)槊總€(gè)值類型對內(nèi)存的要求都不同,所以要為每個(gè)值類型實(shí)例化一個(gè)新類心包。
注:【本段文字來自于《C#高級編程(第10版)》中的”不同的特定類型實(shí)例化泛型時(shí)創(chuàng)建了多少代碼“相關(guān)描述】
泛型類型命名約定
- 泛型類型的名稱用字母
T
作為前綴类咧。 - 如果沒有特殊的要求,泛型類型允許用任意類替代蟹腾,且只使用了一個(gè)泛型類型痕惋,就可以用字符T作為泛型類型的名稱。例如:
public class List<T>{}
- 如果泛型類型有特定的要求(例如娃殖,它必須實(shí)現(xiàn)一個(gè)接口或派生自基類)值戳,或者使用了兩個(gè)或多個(gè)泛型類型,就應(yīng)給泛型類型使用描述性的 名稱炉爆。例如:
public class SortedList<Tkey,TValue>{}
泛型類
泛型類型:也被稱為泛型類型參數(shù)述寡,它是在實(shí)例化泛型類的一個(gè)變量時(shí),泛型聲明中指定的特定類型的占位符叶洞,即泛型類中指定的T鲫凶。
泛型類:定義泛型類型的類,例如List<T>
衩辟,它無法按原樣使用螟炫,因?yàn)樗皇钦嬲念愋停凰袷穷愋偷乃{(lán)圖艺晴。 若要使用 List<T>
昼钻,客戶端代碼必須通過指定尖括號內(nèi)的類型參數(shù)來聲明并實(shí)例化構(gòu)造類型掸屡。
創(chuàng)建泛型類
通常,創(chuàng)建泛型類是從現(xiàn)有具體類開始然评,然后每次逐個(gè)將類型更改為類型參數(shù)仅财,直到泛化和可用性達(dá)到最佳平衡。 在創(chuàng)建泛型類之前碗淌,先建立一個(gè)簡單的普通類盏求,然后再把這個(gè)類轉(zhuǎn)化為泛型類。
定義一個(gè)一般的亿眠、非泛型的簡化鏈表類:
public class LinkedListNode
{
public object Value { get; private set; }
public LinkedListNode(object value)
{
Value = value;
}
public LinkedListNode Prev { get; internal set; }
public LinkedListNode Next { get; internal set; }
}
public class LinkedList : IEnumerable
{
public LinkedListNode First { get; private set; }
public LinkedListNode Last { get; private set; }
//在鏈表尾部添加一個(gè)新元素
public LinkedListNode AddLast(object node)
{
var newNode = new LinkedListNode(node);
if (First == null)
{
First = newNode;
Last = First;
}
else
{
LinkedListNode previous = Last;
Last.Next = newNode;
Last = newNode;
Last.Prev = previous;
}
return newNode;
}
//實(shí)現(xiàn)GetEnumerator()方法
public IEnumerator GetEnumerator()
{
LinkedListNode current = First;
while (current != null)
{
//使用yield語句創(chuàng)建一個(gè)枚舉器類型
yield return current.Value;
current = current.Next;
}
}
}
當(dāng)調(diào)用上述LinkedList
類的AddLast()
方法傳入任意類型的值時(shí)碎罚,會進(jìn)行一系列的裝箱和拆箱的操作
var list1 = new LinkedList();
list1.AddLast(2);
list1.AddLast(3);
list1.AddLast("4");
foreach (var i in list1)
{
Console.WriteLine(i);
}
使用泛型定義上述類
public class LinkedListNode<T>
{
public LinkedListNode(T value)
{
Value = value;
}
public LinkedListNode<T> Next { get; internal set; }
public LinkedListNode<T> Prev { get; internal set; }
public T Value { get; private set; }
}
public class LinkedList<T> : IEnumerable<T>
{
public LinkedListNode<T> First { get; private set; }
public LinkedListNode<T> Last { get; private set; }
public LinkedListNode<T> AddLast(T node)
{
var newNode = new LinkedListNode<T>(node);
if (First == null)
{
First = newNode;
Last = First;
}
else
{
LinkedListNode<T> previous = Last;
Last.Next = newNode;
Last = newNode;
Last.Prev = previous;
}
return newNode;
}
public IEnumerator<T> GetEnumerator()
{
LinkedListNode<T> current = First;
while (current != null)
{
yield return current.Value;
current = current.Next;
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
泛型類的定義與一般類類似,只是要使用泛型類型聲明纳像。聲明后的泛型類型可以在類中用作方法或字段成員的參數(shù)類型荆烈。
調(diào)用上述中聲明的方法用例如下,此時(shí)添加元素和遍歷元素時(shí)都不用頻繁的裝箱和拆箱:
var list2 = new LinkedList<string>();
list2.AddLast("java");
list2.AddLast("c#");
list2.AddLast("python");
foreach (string i in list2)
{
Console.WriteLine(i);
}
泛型類功能
在創(chuàng)建泛型類時(shí)竟趾,可以為泛型類型指定默認(rèn)值憔购、約束、繼承和靜態(tài)成員等岔帽。
創(chuàng)建如下一個(gè)簡單泛型類 倦始,用于從隊(duì)列中讀寫文檔。
public class DocumentManager<T>
{
private readonly Queue<T> documentQueue = new Queue<T>();
public bool IsDocumentAvailable => documentQueue.Count > 0;
public void AddDocument(T doc)
{
lock (this)
{
documentQueue.Enqueue(doc);
}
}
}
//定義一個(gè)簡單的接口
public interface IDocument
{
string Title { get; set; }
string Content { get; set; }
}
//實(shí)現(xiàn)該接口
public class Document : IDocument
{
public Document(string title, string content)
{
this.Title = title;
this.Content = content;
}
public string Content { get; set; }
public string Title { get; set; }
}
泛型類型默認(rèn)值
在上述類DocumentManager<T>
中添加如下方法:
public T GetDocument()
{
//default將泛型類型的值初始化為null或者0山卦,取決于泛型類型是引用類型還是值類型鞋邑。
T doc = default(T);
lock (this)
{
doc = documentQueue.Dequeue();
}
return doc;
}
該方法直接返回類型T
的值,由于不能把null
賦予泛型類型账蓉,原因是泛型類型可以實(shí)例化為值類型枚碗,而null
只能用于引用類型,因此為了解決這個(gè)問題铸本,使用了default
關(guān)鍵字來代替T doc=null;
通過default
關(guān)鍵字肮雨,可以自動的將null
賦予引用類型,將0
賦予值類型箱玷,而不用管T
具體是哪種類型怨规。
泛型類型約束
如果泛型類(定義泛型類型的類,如DocumentManager
)需要調(diào)用泛型類型(T)中的方法锡足,就必須添加約束波丰。
例如,在泛型類DocumentManager<T>
中添加DisplayAllDocuments()
方法用于顯示泛型類型T對應(yīng)的Title值舶得,需要強(qiáng)制進(jìn)行類型轉(zhuǎn)換掰烟,如下:
public void DisplayAllDocuments()
{
foreach (T doc in documentQueue)
{
Console.WriteLine(((IDocument)doc).Title);
}
}
一旦類型T
沒有實(shí)現(xiàn)IDocument
接口,上述類型轉(zhuǎn)換就會存在錯(cuò)誤,此時(shí)最好的做法就是為泛型類添加一個(gè)約束:T
必須實(shí)現(xiàn)IDocument
接口纫骑。
public class DocumentManager<TDocument> where TDocument : IDocument
{
private readonly Queue<TDocument> documentQueue = new Queue<TDocument>();
public bool IsDocumentAvailable => documentQueue.Count > 0;
public void AddDocument(TDocument doc)
{
lock (this)
{
documentQueue.Enqueue(doc);
}
}
public TDocument GetDocument()
{
//default將泛型類型的值初始化為null或者0蝎亚,取決于泛型類型是引用類型還是值類型。
TDocument doc = default(TDocument);
lock (this)
{
doc = documentQueue.Dequeue();
}
return doc;
}
public void DisplayAllDocuments()
{
foreach (TDocument doc in documentQueue)
{
Console.WriteLine(doc.Title);
}
}
}
注意:給泛型類型 添加約束時(shí)先馆,最好包含泛型參數(shù)名稱的一些信息发框,此示例使用TDocument
來代替T
。調(diào)用上述代碼如下:
var dm = new DocumentManager<Document>();
dm.AddDocument(new Document("title A", "sample A"));
dm.AddDocument(new Document("title B", "sample B"));
dm.DisplayAllDocuments();
if (dm.IsDocumentAvailable)
{
Document d = dm.GetDocument();
Console.WriteLine(d.Content);
}
泛型類型支持的約束類型
約束 | 說明 |
---|---|
where T:struct |
類型參數(shù)必須是值類型煤墙。 可以指定除Nullable 以外的任何值類型梅惯。 |
where T : unmanaged |
unmanaged 約束指定類型參數(shù)必須為“非托管類型”。 “非托管類型”不是引用類型番捂,所以該約束指定類型參數(shù)不能是引用類型,并且任何嵌套級別均不能包含任何引用類型成員江解。 |
where T:class |
類約束指定類型T 必須是引用類型 设预。此約束還應(yīng)用于任何類、接口犁河、委托或數(shù)組類型鳖枕。 |
where T:IFoo |
指定類型T 必須實(shí)現(xiàn)接口IFoo
|
where T:Foo |
指定類型T 必須派生自基類Foo
|
where T:new() |
這是一個(gè)構(gòu)造函數(shù)約束,指定類型T必須有一個(gè)默認(rèn)構(gòu)造函數(shù)桨螺。當(dāng)與其他約束一起使用時(shí)宾符,new() 約束必須最后指定。 |
where T1:T2 |
這個(gè)約束也可以指定灭翔,類型T1 派生自泛型類型T2
|
某些約束是互斥的魏烫。 所有值類型必須具有可訪問的無參數(shù)構(gòu)造函數(shù)。 struct
約束包含 new()
約束肝箱,且 new()
約束不能與 struct
約束結(jié)合使用哄褒。 unmanaged
約束包含 struct
約束。 unmanaged
約束不能與 struct
或 new()
約束結(jié)合使用煌张。
從 C# 7.3 開始呐赡,可使用 unmanaged
約束來指定類型參數(shù)必須為“非托管類型”。 “非托管類型”不是引用類型骏融,且任何嵌套級別都不包含引用類型字段链嘀。
注意:只能為默認(rèn)構(gòu)造函數(shù)定義構(gòu)造函數(shù)約束,不能為其他構(gòu)造函數(shù)定義構(gòu)造函數(shù)約束档玻。
使用泛型類型可以合并多個(gè)約束:
public class MyClass<T>
where T : IFoo, new(){ }
上述聲明表示類型T必須實(shí)現(xiàn)IFoo
接口怀泊,且必須有一個(gè)默認(rèn)構(gòu)造函數(shù)。
注意:在C#中误趴,where
子句不能定義必須由泛型類型實(shí)現(xiàn)的運(yùn)算符包个。運(yùn)算符不能在接口中定義。在where
子句中,只能定義基類碧囊、接口树灶、和默認(rèn)構(gòu)造函數(shù)。
泛型類型繼承
泛型類型可以實(shí)現(xiàn)泛型接口糯而,也可以派生自一個(gè)泛型基類天通,其要求是必須重復(fù)基類的泛型類型,或者必須指定基類的類型熄驼。
public class Base<T> { }
public class Derived<T> : Base<T> { }
public class Derived_2<T> : Base<string> { }
派生類(子類)可以是泛型類或非泛型類像寒,例如,可以定義一個(gè)抽象的泛型基類瓜贾,它在派生類中用一個(gè)具體的類實(shí)現(xiàn):
public abstract class Calc<T>
{
public abstract T Add(T x, T y);
public abstract T Sub(T x, T y);
}
public class IntCalc : Calc<int>
{
public override int Add(int x, int y) => x + y;
public override int Sub(int x, int y) => x - y;
}
還可以創(chuàng)建一個(gè)部分的特殊操作诺祸,如下:
public class Query<TRequest, TResult> { }
public class StringQuery<TRequest> : Query<TRequest, string> { }
上述中StringQuery
繼承自Query
,只定義了一個(gè)泛型參數(shù)祭芦,為基類的TResult
指定為string
筷笨,要實(shí)例化StringQuery
,只需要提供TRequest
的類型龟劲。
泛型靜態(tài)成員
應(yīng)該減少泛型靜態(tài)成員的使用胃夏,泛型類的靜態(tài)成員只能在對應(yīng)的同一個(gè)類實(shí)例中共享。
public class StaticDemo<T>
{
public static T x; //此處變量x為T類型
public static int y;
//泛型靜態(tài)成員調(diào)用
StaticDemo<string>.x = "abc";
StaticDemo<int>.x = 13;
StaticDemo<string>.y = 2;
StaticDemo<int>.y = 10;
Console.WriteLine(StaticDemo<string>.x); //結(jié)果:abc
Console.WriteLine(StaticDemo<int>.x); //結(jié)果:13
Console.WriteLine(StaticDemo<string>.y); //結(jié)果:2
Console.WriteLine(StaticDemo<int>.y); //結(jié)果:10
}
泛型接口
使用泛型可以定義接口昌跌,在接口中定義的方法可以帶泛型參數(shù)仰禀。.NET提供了許多泛型接口,同一個(gè)接口常常存在比較老的非泛型版本蚕愤,建議在實(shí)際使用中答恶,優(yōu)先采用泛型版本去解決問題。
泛型接口中的協(xié)變和逆變
為了更好的解釋協(xié)變和逆變的概念萍诱,我們使用List
亥宿、IList
、IEnumerable
三者做一個(gè)簡單的測驗(yàn)砂沛。首先我們定義一個(gè)List<string>
實(shí)例變量listA
烫扼,并將listA
的值指向IList<string>
的變量iListA
,同時(shí)分別使用IEnumerable<string>
去引用這兩個(gè)變量碍庵。
List<string> listA = new List<string>();
IList<string> iListA = listA;
IEnumerable<string> iEnumerableA = listA;
IEnumerable<string> iEnumerableB = iListA;
此時(shí)代碼不會產(chǎn)生錯(cuò)誤映企,能夠正常編譯。因?yàn)?code>List<T>派生自IList<T>
和IEnumerable<T>
静浴,IList<T>
派生自IEnumerable<T>
堰氓,父類引用指向子類對象,所以代碼可以通過編譯苹享。
注意:IEnumerable<T>
實(shí)際上是一個(gè)變體双絮,查看定義的源碼如下:
public interface IEnumerable<out T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
}
特別需要注意的是泛型類型T前面的out
關(guān)鍵字浴麻,它代表的就是協(xié)變。它的作用是什么囤攀?
C#中的string
派生自Object
類型软免,假設(shè)我們也想通過object
的集合直接去引用string
的List
,類似于如下代碼:
List<object> listB = new List<string>(); //報(bào)錯(cuò)焚挠,不會通過編譯
IList<object> iListB = new List<string>(); //報(bào)錯(cuò)膏萧,不會通過編譯
上述的兩條語句均會編譯失敗,因?yàn)?code>List<T>和IList<T>
在泛型定義時(shí)均沒有指定out
關(guān)鍵字蝌衔。而使用IEnumerable<T>
可以通過編譯:
IEnumerable<object> iEnumerableB = new List<string>();//代碼可以正常編譯
上述語句可以通過編譯榛泛。
注意:只有引用類型才支持使用泛型接口中的變體。 值類型不支持變體噩斟。 如下語句將會編譯報(bào)錯(cuò):
IEnumerable<object> integers = new List<int>();//編譯錯(cuò)誤曹锨,值類型不支持變體
下面將用具體的示例對協(xié)變和逆變做詳細(xì)說明。首先定義兩個(gè)簡單的類剃允,其中Rectangle
繼承自父類Shape
拂檩。
public class Shape
{
public double Width { get; set; }
public double Height { get; set; }
//重寫Object的ToString方法
public override string ToString() => $"Width:{Width},Height:{Height}";
}
//定義子類Rectangle
public class Rectangle : Shape { }
泛型接口的協(xié)變
如果泛型類型使用out
關(guān)鍵字標(biāo)注蠢棱,該泛型接口就是協(xié)變的幻赚。
public interface IIndex<out T>
{
//定義一個(gè)索引器
T this[int index] { get; }
int Count { get; }
}
public class RectangleCollection : IIndex<Rectangle>
{
private Rectangle[] data = new Rectangle[3] {
new Rectangle{Height=2,Width=5},
new Rectangle{ Height=3, Width=7},
new Rectangle{ Height=4.5, Width=2.9}
};
public int Count => data.Length;
public Rectangle this[int index]
{
get
{
if (index < 0 || index > data.Length)
{
throw new ArgumentOutOfRangeException("index");
}
return data[index];
}
}
}
上述定義了一個(gè)泛型接口IIndex
靶橱,并使用out
標(biāo)注為協(xié)變佳遂,接著定義類RectangleCollection
實(shí)現(xiàn)該接口营袜,調(diào)用上述代碼如下:
IIndex<Rectangle> rectangles = new RectangleCollection();
//由于采用了協(xié)變,此處可以直接使用父類Shape相關(guān)的引用指向子類Rectangle相關(guān)的對象
IIndex<Shape> shapes = rectangles;
IIndex<Shape> shapes2 = new RectangleCollection();
for (int i = 0; i < shapes.Count; i++)
{
Console.WriteLine(shapes[i]);
}
泛型接口的逆變
使用in關(guān)鍵字標(biāo)注泛型類型的接口就是逆變的丑罪。
public interface IDisplay<in T>
{
void Show(T item);
}
public class ShapeDisplay : IDisplay<Shape>
{
public void Show(Shape item)
{
Console.WriteLine($"{item.GetType().Name} Width:{item.Width},Height:{item.Height}");
}
}
上述定義了一個(gè)逆變的泛型接口IDisplay
荚板,并使用ShapeDisplay
實(shí)現(xiàn)它,注意實(shí)現(xiàn)時(shí)指定的類型是Shape
吩屹,并且定義了Show
方法跪另,顯示對應(yīng)Type
名。調(diào)用代碼如下:
IDisplay<Shape> sd = new ShapeDisplay();
//由于采用了逆變煤搜,可以使用Rectangle相關(guān)的引用指向父類Shape相關(guān)的對象
IDisplay<Rectangle> rectangleDisplay = sd;
rectangleDisplay.Show(rectangles[0]); //Type將會輸出為Rectangle
下面是我自己的理解做的一個(gè)總結(jié)
協(xié)變:使用out
關(guān)鍵字標(biāo)注免绿,協(xié)助變換,既然是協(xié)助就說明是客觀存在的擦盾,也就是順應(yīng)"父類引用指向子類對象"這一原則所做的轉(zhuǎn)換嘲驾,協(xié)變會保留分配兼容性。協(xié)變允許方法具有的返回類型比接口的泛型類型參數(shù)定義的返回類型的派生程度更大迹卢。 在.net中辽故,大多數(shù)的參數(shù)類型類似于協(xié)變 ,比如定義了一個(gè)方法腐碱,參數(shù)為object
誊垢,調(diào)用該方法時(shí),可以為參數(shù)傳入所有object
派生出的子類對象。
逆變:逆反變換喂走,違背”父類引用指向子類對象“這一原則所做的轉(zhuǎn)換殃饿,和協(xié)變相反,類似于“子類引用父類相關(guān)的對象”缴啡。逆變允許方法具有的實(shí)參類型比接口的泛型形參定義的類型的派生程度更小壁晒。比如定義一個(gè)方法,方法的參數(shù)為object
业栅,返回的類型為object
的子類秒咐,此時(shí)不能直接返回傳入的參數(shù),必須進(jìn)行類型轉(zhuǎn)換碘裕,而逆變可以很好的解決此類問題携取。
變體:如果泛型接口或委托的泛型參數(shù)被聲明為協(xié)變或逆變,該泛型接口或委托則被稱為“變體”帮孔。
泛型方法
在泛型方法中雷滋,泛型類型用方法聲明來定義。泛型方法可以在非泛型類中定義文兢。如下晤斩,定義一個(gè)簡單的泛型方法:
void Swap<T>(ref T x,ref T y)
{
T temp;
temp = x;
x = y;
y = temp;
}
注意定義的形式,泛型類型T
需要在方法聲明中(方法名的后面)指定姆坚。調(diào)用上述方法代碼:
int a = 1, b = 2;
Swap<int>(ref a, ref b);
//C#編譯器會通過調(diào)用該方法來獲取參數(shù)的類型澳泵,所以不需要把泛型類型賦予方法調(diào)用,可簡化為下述語句
Swap(ref a, ref b); //上述語句的簡化寫法
在調(diào)用泛型方法時(shí)兼呵,C#編譯器會根據(jù)傳入的參數(shù)自動獲取類型兔辅,因此不需要把泛型類型賦予方法調(diào)用,即Swap<int>
中的<int>
可以不用指定(實(shí)際編碼中击喂,可以借助VS智能編碼助手進(jìn)行簡化维苔,使用ctrl+.
快捷鍵進(jìn)行調(diào)用)
帶約束的泛型方法
在泛型類中,泛型類型可以用where子句來限制懂昂,同樣介时,在泛型方法,也可以使用where
子句來限制泛型類型凌彬。
public interface IAccount
{
decimal Balance { get; }
string Name { get; }
}
public class Account : IAccount
{
public Account(string name, decimal balance)
{
Name = name;
Balance = balance;
}
public decimal Balance { get; private set; }
public string Name { get; }
}
上述定義一個(gè)簡單的接口和實(shí)現(xiàn)的類沸柔,接著定義一個(gè)泛型方法,并且添加where子句約束饿序,讓泛型類型TAccount
對應(yīng)的傳入?yún)?shù)必須實(shí)現(xiàn)接口IAccount
勉失。
//靜態(tài)類不能被實(shí)例化
public static class Algorithms
{
public static decimal Accumulate<TAccount>(IEnumerable<TAccount> source)
where TAccount : IAccount
{
decimal sum = 0;
foreach (TAccount a in source)
{
sum += a.Balance;
}
return sum;
}
}
調(diào)用上述方法的代碼:
var accounts = new List<Account> {
new Account("書籍",234),
new Account("文具",56),
new Account("手機(jī)",2300)
};
//decimal amount = Algorithms.Accumulate<Account>(accounts);
//編譯器會從方法的參數(shù)類型中自動推斷出泛型類型參數(shù),可以簡化為下述代碼進(jìn)行調(diào)用
decimal amount = Algorithms.Accumulate(accounts);
注意:并不是所有的方法調(diào)用都不需要指定泛型參數(shù)類型原探,當(dāng)編譯器無法自動推斷出類型時(shí)乱凿,需要顯式的進(jìn)行指定顽素,比如帶委托的泛型方法。
泛型委托
這里我們用一個(gè)簡單的例子來說明一下泛型委托的調(diào)用徒蟆。關(guān)于委托胁出,后續(xù)我會單獨(dú)進(jìn)行總結(jié)。
public static TSum Accumulate<TAccount, TSum>(
IEnumerable<TAccount> source, //方法第一個(gè)參數(shù)
Func<TAccount, TSum, TSum> action //方法第二個(gè)參數(shù)是一個(gè)委托
) where TAccount : IAccount where TSum : struct
{
TSum sum = default(TSum);
foreach (TAccount item in source)
{
sum = action(item, sum);
}
return sum;
}
該方法在聲明時(shí)段审,指定了兩個(gè)泛型類型TSum
和TAccount
全蝶,其中一個(gè)約束是值類型,一個(gè)約束是實(shí)現(xiàn)接口IAccount
寺枉,傳入的第一個(gè)參數(shù)是IEnumerable<TAccount>
類型的抑淫,第二個(gè)參數(shù)是一個(gè)委托。在調(diào)用該方法時(shí)姥闪,編譯器不能自動推斷出參數(shù)類型始苇,需要顯式的指定泛型參數(shù)類型,調(diào)用該方法代碼如下:
var accounts = new List<Account> {
new Account("書籍",234),
new Account("文具",56),
new Account("手機(jī)",2300)
};
decimal amount = Algorithms.Accumulate<Account, decimal>(
accounts, //傳入的參數(shù)1
(item, sum) => sum += item.Balance //傳入的參數(shù)2
);
本文后續(xù)會隨著知識的積累不斷補(bǔ)充和更新筐喳,內(nèi)容如有錯(cuò)誤催式,歡迎指正。
最后一次更新時(shí)間:2018-06-28
參考資源:
- 《C#高級編程(第10版)》
- C#編程指南——泛型
- C#泛型中的協(xié)變和逆變
- .NET中的泛型