C#基礎(chǔ)提升系列——C#泛型

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 約束不能與 structnew() 約束結(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亥宿、IListIEnumerable三者做一個(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的集合直接去引用stringList,類似于如下代碼:

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è)泛型類型TSumTAccount全蝶,其中一個(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

參考資源:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末避归,一起剝皮案震驚了整個(gè)濱河市荣月,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌梳毙,老刑警劉巖哺窄,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異顿天,居然都是意外死亡堂氯,警方通過查閱死者的電腦和手機(jī)蔑担,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進(jìn)店門牌废,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人啤握,你說我怎么就攤上這事鸟缕。” “怎么了排抬?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵懂从,是天一觀的道長。 經(jīng)常有香客問我蹲蒲,道長番甩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任届搁,我火速辦了婚禮缘薛,結(jié)果婚禮上窍育,老公的妹妹穿的比我還像新娘。我一直安慰自己宴胧,他們只是感情好漱抓,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著恕齐,像睡著了一般乞娄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上显歧,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天仪或,我揣著相機(jī)與錄音,去河邊找鬼士骤。 笑死溶其,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的敦间。 我是一名探鬼主播瓶逃,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼廓块!你這毒婦竟也來了厢绝?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤带猴,失蹤者是張志新(化名)和其女友劉穎昔汉,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拴清,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡靶病,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了口予。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片娄周。...
    茶點(diǎn)故事閱讀 38,716評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖沪停,靈堂內(nèi)的尸體忽然破棺而出煤辨,到底是詐尸還是另有隱情,我是刑警寧澤木张,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布众辨,位于F島的核電站,受9級特大地震影響舷礼,放射性物質(zhì)發(fā)生泄漏鹃彻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一妻献、第九天 我趴在偏房一處隱蔽的房頂上張望蛛株。 院中可真熱鬧虚婿,春花似錦、人聲如沸泳挥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽屉符。三九已至剧浸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間矗钟,已是汗流浹背唆香。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留吨艇,地道東北人躬它。 一個(gè)月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像东涡,于是被迫代替她去往敵國和親冯吓。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評論 2 350

推薦閱讀更多精彩內(nèi)容