C#中的泛型

一、沒有泛型之前

在沒有泛型之前,我們是怎么處理不同類型的相同操作的:

示例1
 //下面是一個處理string類型的集合類型
 public class MyStringList
    {
        string[] _list;
        public void Add(string x)
        {
            //將x添加到_list中谭企,省略實(shí)現(xiàn)
        }
        public string this[int index]
        {
            get { return _list[index]; }
        }
    }
 //調(diào)用
 MyStringList myStringList = new MyStringList();
 myStringList.Add("abc");
 var str = myStringList[0];
示例2
    //如果我們需要處理int類型就需要復(fù)制粘貼然后把string類型替換為int類型:
    public class MyIntList
    {
        int [] _list;
        public void Add(int x)
        {
            //將x添加到_list中廓译,省略實(shí)現(xiàn)
        }
        public int this[int index]
        {
            get { return _list[index]; }
        }
    }
   //調(diào)用
    MyIntList myIntList = new MyIntList();
    myIntList.Add(100);
    var num = myIntList[0];

可以看得出我們的代碼大部分是重復(fù)的,而作為有追求的程序員是不允許發(fā)生這樣的事情的债查。
于是乎非区,我們做了如下改變:

示例3
 public class MyObjList
    {
        object[] _list;
        public void Add(object x)
        {
            //將x添加到_list中,省略實(shí)現(xiàn)
        }
        public object this[int index]
        {
            get { return _list[index]; }
        }
    }
 //調(diào)用
 MyObjList myObjList = new MyObjList();
myObjList.Add(100);
 var num = (int)myObjList[0];

從上面這三段代碼中攀操,我們可以看出一些問題:

  1. int和string集合類型的代碼大量重復(fù)(維護(hù)難度大)院仿。
  2. object集合類型發(fā)生了裝箱和拆箱(損耗性能)。
  3. object集合類型是存在安全隱患的(類型不安全)速和。

問題1歹垫,雖然代碼重復(fù)但是沒有裝箱、拆箱而且類型是安全的
問題2颠放,發(fā)生了裝箱和拆箱排惨,是損耗性能影響執(zhí)行效率的。
問題3碰凶,如果add的類型不是int類型暮芭,在編譯器是不會檢查出來的(編譯通過),運(yùn)行期就會報(bào)錯,MyObjList類似于我們熟知的ArrayList

運(yùn)行期報(bào)錯

現(xiàn)在欲低,我們必須解決如下問題
1辕宏、避免代碼重復(fù)
2、避免裝箱和拆箱
3砾莱、保證類型安全

范型為我們提供了完美的解決方案

二瑞筐、什么是泛型

如果你理解類是對象的模板(類是具有相同屬性和行為的對象的抽象),那么泛型就很好理解了腊瑟。
泛型:generic paradigm(通用的范式)聚假,generic這個單詞也很好的說明了模板這個概念:通用的,標(biāo)準(zhǔn)的闰非。
泛型是類型的模板
不同的是:作為模板的類是通過實(shí)例化產(chǎn)生不同的對象膘格,而泛型是通過不同的類型實(shí)參產(chǎn)生不同的類型
泛型的基本概念介紹完,我們來看看泛型到底是怎么幫我們解決問題的

如何解決代碼重復(fù):提取代碼相同的部分财松,封裝變換的部分——封裝變化瘪贱,而示例1和示例2中變換的部分就是int和string類型本身,如何將類型抽象呢

示例4
    //將示例3改裝下
    public class MyList<T>
    {
        T [] _list;
        public void Add(T x)
        {
            //將x添加到_list中辆毡,省略實(shí)現(xiàn)
        }
        public T this[int index]
        {
            get { return _list[index]; }
        }
    }

類型參數(shù) T
類型參數(shù)可以理解為泛型的"形參"("形參"一般用來形容方法的)政敢,有“形參”就會有實(shí)參。如我們聲明的List<string>胚迫,string就是實(shí)參;List<int> ,int就是實(shí)參,而List<string>和List<int>是兩種不同的類型唾那。

不同的類型

通過類型參數(shù)解決了代碼重復(fù)的問題

如何解決裝箱访锻、拆箱以及類型安全的問題:

 //示例5
       List<int> list = new List<int>();
       list.Add(100);//強(qiáng)類型無需裝箱
       //list.Add("ABC"); 編譯期安全檢查報(bào)錯
       int num = list[0];//無需拆箱   
編譯期安全檢查報(bào)錯

聲明泛型類型時褪尝,因?yàn)榇_定了類型實(shí)參,所以操作泛型類型不需要裝箱期犬、拆箱河哑,而且泛型將大量安全檢查從運(yùn)行時轉(zhuǎn)移到了編譯時進(jìn)行,保證了類型安全龟虎。
注:C#為我們提供了5種泛型:類璃谨、結(jié)構(gòu)、接口鲤妥、委托和方法佳吞。

在示例4中,自定義泛型集合只是添加和獲取類型參數(shù)的實(shí)例棉安,除此之外底扳,沒有對類型參數(shù)實(shí)例的成員做任何操作。C#的所有類型都繼承自O(shè)bject類型贡耽,也就是說衷模,我們目前只能操作Object中的成員(Equals,GetType,ToString等)。但是蒲赂,我自定義的泛型很多時候是需要操作類型更多的成員

新需求阱冶,打印員工的信息

示例6
    public class Person
    {
        public string Name { get; set; }
        public int Age{ get; set; }
    }
    public class Employee : Person {  }
    public class PrintEmployeeInfo<T>
    {
        public void Print(T t)
        {
            Console.WriteLine(t.Name);//報(bào)錯
        }
    }
示例6:T未包含“Name”的定義

如果我們可以將類型參數(shù)T限定為Person類型,那么在泛型內(nèi)部就可以操作Person類型的成員了滥嘴。

三木蹬、泛型的約束

表格來至微軟官方文檔

約束 描述
where T:結(jié)構(gòu) 類型參數(shù)必須是值類型。 可以指定除 Nullable 以外的任何值類型氏涩。
where T:類 類型參數(shù)必須是引用類型届囚;這同樣適用于所有類、接口是尖、委托或數(shù)組類型意系。
where T:new() 類型參數(shù)必須具有公共無參數(shù)構(gòu)造函數(shù)。 與其他約束一起使用時饺汹,new() 約束必須最后指定蛔添。
where T:<基類名稱> 類型參數(shù)必須是指定的基類或派生自指定的基類。
where T:<接口名稱> 類型參數(shù)必須是指定的接口或?qū)崿F(xiàn)指定的接口兜辞。 可指定多個接口約束迎瞧。 約束接口也可以是泛型。
where T:U 為 T 提供的類型參數(shù)必須是為 U 提供的參數(shù)或派生自為 U 提供的參數(shù)逸吵。
示例7
 public class PrintEmployeeInfo<T> where T:Person
    {
        public void Print(T t)
        {
            Console.WriteLine(t.Name);
        }
    }

四凶硅、協(xié)變和逆變很簡單

有一定工作經(jīng)驗(yàn)的開發(fā)人員一定遇到過下面這樣的情況:

示例8
 List<Employee> list = new List<Employee>();
 list.Add(new Employee() { Age = 20, Name = "小明" });
 IEnumerable<Person> perList;
 perList = list;
 foreach (var item in perList)
 {
     Console.WriteLine("名字:" + item.Name + ",年齡:" + item.Age);
 }

不是說,不同類型實(shí)參構(gòu)造的泛型也是不同的嗎扫皱,為啥可以將List<Employee>對象賦值給IEnumerable<Person>呢?
再看下面的示例

示例9
  public static void PrintEmployee(Person item)
  {
      Console.WriteLine("名字:" + item.Name + ",年齡:" + item.Age);
  }

  Action<Employee> empAction = PrintEmployee;
  empAction(new Employee() { Age = 20, Name = "小明" });

  Action<Person> perAction = PrintEmployee;
  perAction(new Employee() { Age = 20, Name = "小明" });

執(zhí)行結(jié)果正常輸出

正常輸出

為什么可以將參數(shù)類型為Person的方法分別賦值給Action<Person>和Action<Employee>呢足绅?
示例8說明了泛型的協(xié)變性捷绑,示例9說明了泛型的逆變性(聽起來很唬人)
其實(shí)協(xié)變和逆變只要弄清楚兩個概念一切就非常清晰了

  1. 類型參數(shù)分為輸入?yún)?shù)(in)、輸出參數(shù)(out)和不變參數(shù)(沒有關(guān)鍵字)
  2. 設(shè)計(jì)原則:里氏替換原則——派生類(子類)對象能夠替換其基類(超類)對象被使用

IEnumerable的定義
public interface IEnumerable<out T> : IEnumerable
示例8中IEnumerable<Person>輸出參數(shù)類型需要的Person類型氢妈,而List類型參數(shù)給的是Employee(Employee繼承了Person)——里氏替換原則
委托Action的定義
public delegate void Action<in T>(T obj);
示例9中方法PrintEmployee需要的參數(shù)類型是Person粹污,而Action<Employee>輸入類型參數(shù)是Employee(Employee繼承了Person)——里氏替換原則
如果將PrintEmployee的參數(shù)類型變?yōu)镋mployee,示例9中其他代碼不變首量,會怎樣壮吩?

編譯錯誤

清楚的錯誤信息

方法PrintEmployee需要的參數(shù)類型是Employee,而Action的輸入?yún)?shù)是Person加缘,顯然Person不一定是Employee

注:in和out關(guān)鍵字只適用于接口和委托類型

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鸭叙,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子生百,更是在濱河造成了極大的恐慌递雀,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蚀浆,死亡現(xiàn)場離奇詭異缀程,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)市俊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門杨凑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人摆昧,你說我怎么就攤上這事撩满。” “怎么了绅你?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵伺帘,是天一觀的道長。 經(jīng)常有香客問我忌锯,道長伪嫁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任偶垮,我火速辦了婚禮张咳,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘似舵。我一直安慰自己脚猾,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布砚哗。 她就那樣靜靜地躺著龙助,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蛛芥。 梳的紋絲不亂的頭發(fā)上泌参,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天脆淹,我揣著相機(jī)與錄音,去河邊找鬼沽一。 笑死,一個胖子當(dāng)著我的面吹牛漓糙,可吹牛的內(nèi)容都是我干的铣缠。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼昆禽,長吁一口氣:“原來是場噩夢啊……” “哼蝗蛙!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起醉鳖,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤捡硅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后盗棵,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體壮韭,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年纹因,在試婚紗的時候發(fā)現(xiàn)自己被綠了喷屋。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡瞭恰,死狀恐怖屯曹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情惊畏,我是刑警寧澤恶耽,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站颜启,受9級特大地震影響偷俭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜农曲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一社搅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧乳规,春花似錦形葬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至冻辩,卻和暖如春猖腕,著一層夾襖步出監(jiān)牢的瞬間拆祈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工倘感, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留放坏,地道東北人。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓老玛,卻偏偏與公主長得像淤年,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蜡豹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評論 2 353

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