讓泛型的思維扎根在腦海——深刻理解泛型

1.前言

往往一些剛接觸C#編程的初學者,對于泛型的認識就是直接跳到對泛型集合的使用上,雖然微軟為我們提供了很多內置的泛型類型,但是如果我們只是片面的了解調用方式,這會導致我們對泛型盲目的使用。至于為什么要使用泛型,什么情況下定義屬于自己的泛型,定義泛型又能為程序帶來哪些好處。要理清這些問題,我們就必須深刻理解泛型的本質,形成泛型編程的思維方式。

接下來我將基于一個基礎示例,然后通過需求不斷的演化示例,從而讓泛型在關鍵時刻脫穎而出,以便讓我們能夠深刻體會泛型的作用。假設.NET沒有為我們提供用于存儲數據的集合,而我們需要一個能夠用于存儲string元素的集合,基于這個情況我們自定義了一個用于存儲字符串的集合類:

class ArraryStr{public ArraryStr(){_items = new string[100]; //初始化存儲元素的容量,只是為了演示故將容量定義為固定值}private string[] _items; //存儲元素的數組private int _count;   //元素總數public int Count{get { return _count; }}public void Add(string item) //新增元素{_items[_count] = item;_count++;}public string this[int index] //索引{get { return _items[index]; }set { _items[index] = value;  }}} // END ArraryStr

為了驗證自定義string集合的可行性,我們對其進行了如下的應用:

1             ArraryStr arraryStr = new ArraryStr();
2             arraryStr.Add("張三");
3             Console.WriteLine(arraryStr[0]);

2.重復

目前對于創建string類型的集合已經大功告成,而此刻我們又接到了一個新的需求,即我們需要一個集合存儲int類型的元素。基于自定義string集合的經驗來看,我們可以發現,string集合類型和我們即將要創建的int集合類型的結構和內容幾乎是一樣的。這就意味著我們可以使用江湖盛行的“復制大法”,將之前的代碼復制一遍,然后輕微修改下即可。下面是兩個集合類型代碼的對比圖。

39b22390ccbadefd3d2d29e0be2b633a.png

在早年有款熱門的游戲叫做“大家來找茬”,該游戲主要玩法就是在兩個大致相同的圖片中,查找兩者之間的細微差異之處。我們使用的“復制大法”,促使我們編寫的代碼形成了可以用于這個游戲游玩的場景。“對于上面的兩個代碼截圖,你能找出圖中不同的地方嗎?”

對于軟件開發者而言,面對的最主要的敵人就是“變化”,假設后面還會出現N個類型的元素需要我們定義集合來存儲,那我們是不是要將相同的代碼無窮盡的復制下去?DRY(Don't Repeat Yourself,不要重復自己,請記住這是作為一名軟件開發者編碼的原則,“復制大法”很明顯的違背了這個原則。


3.安全和性能

通過“復制,粘貼”的手段可以很明顯的感受到我們在做重復的事情,在重復中我們可以發現:集合存儲的類型在增加,但是集合的結構和添加元素的方法都是相同的邏輯。簡單來說就是,不同類型的處理,其處理邏輯都是類似的。基于這個特點,為了滿足自定義集合能夠應對所有類型的存儲,我們必須使用一個通用類型來作為代表,此時此刻我們腦海中就能浮現出一句話:object是一切類型的基類。這就意味著我們添加的所有類型,都可以隱式的轉換為object類型,從而使得自定義集合可以添加任何類型的元素。讓我們來運用這個object類型來試試:

class ArraryList{public ArraryList() { _items = new object[100]; }private object[] _items;private int _count;public int Count{get { return _count; }}public void Add(object item){_items[_count] = item;_count++;}public object this[int index]{get { return _items[index]; }set { _items[index] = value; }}} // END ArraryStrinternal class Program{static void Main(string[] args){ArraryList arraryList = new ArraryList();arraryList.Add("張三");arraryList.Add(18);string name = (string)arraryList[0];int age = (int)arraryList[1];} // END Main()}

在上面的代碼中,我們結合了object是一切類型基類的特點,對集合類型進行改造,并成功的使用該方式的集合添加了不同類型的元素。雖然在使用的角度來看已經完美無缺(可以添加任何類型),但是獲取集合元素進行賦值的時候,還使用了類型強制轉換的手段。這是因為這種方式存在很嚴重的問題,主要包括以下兩個方面:

  1. 類型安全方面,如果集合的第一個元素是sting類型,但是你客觀認為是int類型,于是你在獲取時進行了int類型的強制轉換,這個時候代碼不會提示錯誤且可以正常編譯,那么這就意味著程序在運行時會產生一個你無法預料的類型無效轉換的異常。

  2. 性能方面,值類型元素添加到集合時,必然會存在裝箱操作;而在獲取元素并賦值給一個值類型變量時,又會發生相應的拆箱操作。這種拆箱和裝箱的操作,在操作大量元素時會大幅度的損失程序的性能。

到目前位置,我們還是沒有能創建一個能夠存儲任何類型的集合,但是我們可以對于上述的示例演變的過程進行一個總結:對于不同類型有相同處理邏輯的情況,如果一味的復制會導致我們出現重復代碼,如果使用object來作為解決重復的方案,會存在類型安全和性能的問題。至于如何讓徹底解決這些問題,這就要說到了本文講解的主題——泛型。


4.代碼模板

C#中有兩種不同的機制來編寫跨類型(一個類型代替多個類型)可復用的代碼:繼承和泛型。繼承的復用性來自于基類,而泛型的復用性是通過帶有“占位符”的代碼模板類型實現的。繼承實現復用是站在面向對象的角度思考的,而泛型的復用是站在實現特定功能上思考的。相比于繼承,泛型不用遵循里氏替換原則,并且能夠提高類型的安全性,減少類型轉換帶來的拆箱和裝箱。

怎么樣理解泛型?泛型本質上相當于一種“代碼模板”,可以用一套代碼,為不同類型的同一邏輯使用統一的方式實現。其中“模板”一詞的概念需要進行深刻的體會。例如,公司在招聘時會與用人方簽訂勞動合同,而這個勞動合同的主要內容對于所有人來說幾乎都是一樣的,只是在極個別的地方有所差異,如薪資、姓名等。所以公司不會為某個人(張三或李四)去特意的制定合同,而是會統一制定一份勞動合同作為模板,將其中針對個人存在差異的部分通過“下劃線”進行占位預留,“下劃線”的值將在簽訂合同時由具體的聘用者根據自身情況填寫。

5350e87f8446c3228d1c9445c3622406.png

對于這種模板方式的使用,公司在制定合同時則不用考慮簽訂合同的人具體是誰,因為勞動合同(模板)和使用者是分開的,所以公司只用專注于合同的主要內容即可。而我們在實際的編程運用中,使用泛型的目的,其實和公司制定通用的勞動合同模板是一個道理。假設你的公司需要雇傭100名員工時,你不希望為每一個人都制定一個專屬的合同吧?假設你的代碼中,如果遇到10個類型,它們的操作處理邏輯都一樣時,你不希望為這個10個類型寫10個處理方式吧?

通過上面的介紹和例子,接下來我們將泛型運用到我們的示例中來,代碼如下:

1     class ArraryList<T>2     {3         public ArraryList() { _items = new T[100]; }4 5         private T[] _items;6         private int _count;7         public int Count8         {9             get { return _count; }
10         }
11 
12         public void Add(T item)
13         {
14             _items[_count] = item;
15             _count++;
16         }
17 
18         public T this[int index]
19         {
20             get { return _items[index]; }
21             set { _items[index] = value; }
22         }
23     } // END ArraryStr
24     internal class Program
25     {
26         static void Main(string[] args)
27         {
28             ArraryList<string> arraryStr = new ArraryList<string>();
29             arraryStr.Add("張三");
30             Console.WriteLine(arraryStr[0]);
31 
32             ArraryList<int> arraryInt = new ArraryList<int>();
33             arraryInt.Add(18);
34             Console.WriteLine(arraryInt[0]);
35 
36         } // END Main()
37 
38     }

5.類型參數

在上面的代碼中,我們將集合類型定義為了泛型類,該類型中出現的T屬于泛型中的類型參數(Type Parameter)。泛型為了達到通用處理的目的,所以不能將某個具體類型作為處理的目標類型,故而將要處理的類型用“T”作為一個類型占位符。

“T”并不是真正的數據類型,它更像是泛型使用的類型藍圖,所以在使用時,泛型類型的消費者必須將一個具體類型作為“類型參數”傳遞到尖括號內,以此構造一個有明確處理類型的泛型實例。所以我們在外部使用泛型時不能以:“ArraryList<T>list =new ArraryList<T>()”、“T t=new T()”這種方式去實例化泛型類型。另外,“T”本身僅僅是類型參數的名稱,它只是代表了類型參數的標識而已,這意味著我們可以使用其他字符來為類型參數命名。


6.替換

通過類型參數的使用我們可以得知,泛型類型代碼在靜態階段沒有明確的類型,那么在程序運行的時候,它又是如何和使用時指定的“類型參數”進行對接的呢?為了搞清楚這個問題,下面我們來了解下泛型運行時的本質。

我們編寫的C#程序在編譯后生成的代碼,并不是計算機可以直接執行的代碼,而是會生成CIL(通用中間語言)代碼并包含在程序集中,如果想要生成計算機可執行的代碼,則還需要JIT(即時編譯器)對CIL代碼進行二次編譯。然而泛型類型確認其具體類型的時機,就在JIT進行二次編譯時,JIT編譯的代碼如果包含了泛型的內容,那么它會根據泛型類型的消費者指定的類型參數,將CIL中泛型代碼中的占位符T替換為一個具體的類型,從而明確當前執行的泛型代碼是針對哪個類型來使用的,其中替換的過程是由CLR在運行時進行主導,JIT來實際操作完成的。這個在運行時確認了類型的泛型又被稱之為“封閉類型”,反之在運行時確認之前的泛型稱為“開放類型”。

a38c3915518c1d49479660bdae2ffa5e.png

泛型使用占位符在運行時替換具體類型的機制,其實和本文中例舉勞動合同模板使用“下劃線”的方式有同樣的思想。在指定勞動合同模板時,對于聘用者的姓名并不能寫一個具體的名字,因為模板的目的是為了通用化,所以對于名字采用了“下劃線”的方式。當公司與某個具體的人簽訂合同的時候,勞動合同模板中的下劃線將由聘用者根據自身情況填寫。回到泛型中其使用思想也是如此,我們使用泛型的目的是為了讓多個類型的處理通用化,所以在定義泛型代碼的時候并不能指定一個具體類型,故使用類型參數T進行代替,這個類型參數T就相當于勞動合同模板中的“下劃線”,當泛型在實際運行的時候,JIT會根據泛型消費者指定的具體類型與占位符T進行替換。


7.總結

本文并不是專門適用于介紹泛型的使用細節的文章,而是通過一個實例根據需求不斷演化的過程,對泛型一步步深入,從而更加深刻的理解泛型的使用初衷,相比了解泛型“只言片語”而言,形成泛型的編程概念和思維顯得尤為重要。在泛型的機制中,我們可以將不同類型存在相同處理邏輯的情況,形成一個通用的方案,從而不在為特定的類型進行編碼,用一套通用的代碼模板會服務于更多的類型,并且在使用上能保證類型安全和提供良好的性能。

原文地址:https://www.cnblogs.com/green-jcx/p/16671687.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/282794.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/282794.shtml
英文地址,請注明出處:http://en.pswp.cn/news/282794.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

fgetcsv()函數

fgetcsv()函數。fgetcsv()函數可以讀取指定文件的當前行&#xff0c;使用CSV格式解析出字段&#xff0c;并返回一個包含這些字段的數組。語法格式如下&#xff1a;array fgetcsv(resource $handle [, int $length [, string $delimiter [, string $enclosure[,string $escape]]…

android 系統ui修改器,分享兩個效果 - Android 系統 UI 管理

SystemUIManage.gifDimming the System Bars (沉浸模式)知乎 和 Medium 中都使用到了這個效果&#xff0c;作為沉浸式閱讀模式。// This example uses decor view, but you can use any visible view.View decorView getWindow().getDecorView();int uiOptions View.SYSTEM_U…

打游戲要存進度-備忘錄模式

打游戲要存進度-備忘錄模式 學習自 《大話設計模式》 備忘錄模式漫談 備忘錄的這種設計思想是非常常見的&#xff0c;比如說圍棋游戲的悔棋&#xff0c;繪圖軟件的撤銷功能等等&#xff0c;都或多或少的使用了備忘錄模式來處理對象的狀態。 備忘錄(Memento): 在不破壞封裝性的前…

(10.1)Python學習筆記二

1、在項目工程中要模塊化測試一個開發的功能&#xff0c;在測試通過后交付給項目組其他人員繼續開發。要保證代碼開發的性能和效率以及可擴展性。 2、項目工程中的文件夾分類要功能模塊明確清晰&#xff0c;在python中引入某一個 文件夾下的文件可以使用如下方式&#xff1a; t…

利用lay-ui結合ajax實現分頁功能(不借助框架,簡單易懂)

效果圖: 1.創建html頁面 01.html(前臺文件) 2.創建index.php(后臺文件) ------------------熱身結束,開始正式分頁之旅------------------ 3.在html頁面中引入layui需要用到的css以及js,還有我們自己額外需要用到的jquery 4.在html文件中,將基本的分頁欄顯示出來 5.好啦,htm…

Linux系統壓縮及解壓縮

Linux系統解壓縮概述&#xff1a;本篇將介紹Linux系統中的壓縮和解壓縮的工具&#xff0c;以及歸檔工具&#xff08;tar&#xff0c;cpio&#xff09;compress/uncompress&#xff1a;對應 .Z 結尾的壓縮格式文件&#xff1b;gzip/gunzip&#xff1a;其對應的是 .gz 結尾的壓縮…

酷派手機android版本,系統版本迎來升級

系統版本迎來升級這個應該是兩個版本之間最大但是卻不那么直觀的不同了&#xff0c;因為從TD版酷派大神F1采用的CoolLife UI 5.0版本&#xff0c;再到聯通版酷派大神F1所搭載的CoolLife UI 5.5版本&#xff0c;它們之間經歷了一個比較不錯的升級。在圖標ICON&#xff0c;功能設…

學習RUNOOB.COM進度一

了解MongoDB 由C語言編寫的&#xff0c;是一個基于分布式文件存儲的開源數據庫系統。在高負載的情況下&#xff0c;添加更多的節點&#xff0c;可以保證服務器性能。 特點 面向文檔&#xff0c;操作簡單容易 設置任何索引&#xff0c;實現更快排序 本地或者網絡創建數據鏡像&am…

最終用戶計算安全——特權訪問控制

本篇算是系列的第二篇&#xff0c;之前寫了一篇關于勒索軟件攻擊的&#xff0c;坦白說寫這樣的文很費腦子&#xff0c;而且喜歡看的讀者估計也不多…不過我覺得整理一下思路&#xff0c;對于通過最終用戶計算產品或方案來提升組織安全還是有很大的意義的。所以一邊喝著清茶吃著…

MVC學習九:MVC 特性本質

一、特性的本質就是&#xff1a;對屬性、方法、類加特性&#xff0c;本質就是new 一個特性類對象賦值給屬性、方法、類。 可以通過反射的方式取得特性的值&#xff0c;代碼如下&#xff1a; ①自定義特性 public class MyAttribute:Attribute{public string Name { get; set; }…

前端知識點總結---面試專用

1.關于基礎css html js部分 1.1基本算法 1&#xff09;快速排序 時間復雜度 nlogn function quickSort(arr){if (arr.length<1){return arr;}var pivotIndex 0,pivort arr.splice(pivortIndex, 1)[0];var left [],right [];for (var i 1, length arr.length; i < l…

鴻蒙系統大疆,華為操作系統“鴻蒙OS”來了!

原標題&#xff1a;華為操作系統“鴻蒙OS”來了&#xff01;8月9日&#xff0c;華為消費者業務CEO余承東在華為開發者大會上如期發布了華為鴻蒙操作系統“HarmonyOS”。據介紹&#xff0c;鴻蒙內核在2017年便完成技術驗證&#xff0c;最初是為了提升操作系統的跨平臺能力而研發…

詳述 IntelliJ IDEA 插件的安裝及使用方法

首先&#xff0c;進入插件安裝界面&#xff1a; Mac&#xff1a;IntelliJ IDEA -> Preferences -> Plugins;Windows&#xff1a;File -> Settings -> Plugins.標注 1&#xff1a;顯示 IntelliJ IDEA 的插件分類&#xff0c; All plugins&#xff1a;顯示 IntelliJ …

杭漂兩年,深漂兩年,宇宙的盡頭到底在哪兒

hi&#xff0c;這里是桑小榆。這次分享的是一位杭漂兩年&#xff0c;深漂兩年的碼農伙伴的經歷。首先他能夠在大學期間就尋找到自己的熱愛并持之以恒值得令人學習。其次他的工作經歷可以說是非常的“程序員”&#xff0c;因為程序員所面對的職業生涯中&#xff0c;所謂的實習&a…

侶信即時通訊系統的技術解析

侶信&#xff1a; 說明&#xff1a; 侶信專業版是面向中小企業和者各類團隊組織內部交流使用工具,可以在互聯網或者局域網中使用。具有豐富的功能&#xff0c;聊天&#xff0c;群組&#xff0c;部門組織&#xff0c;內部朋友圈&#xff0c;以及漂流瓶搖一搖等功能。它可以在局域…

Confluence 6 使用 WebDAV 客戶端來對頁面進行操作

下面的部分告訴你如何在不同的系統中來設置原生的 WebDAV 客戶端&#xff0c;這個客戶端通常顯示在你操作系統的文件瀏覽器中&#xff0c;例如&#xff0c;Windows 的 Windows Explorer 或者 Linux 的 Konqueror。在 Mac OSX Finder 中訪問 Confluence你可以成功的連接&#xf…

.Net之接口小知識

目的通過一個簡單的項目&#xff0c;在原來的文章基礎上完善一下常用的幾種WebApi編寫方式以及請求方式&#xff0c;一方面是用于給我一個前端朋友用來學習調用接口&#xff0c;另一方面讓我測試HttpClient的一些效果。本文示例代碼環境&#xff1a;vs2022、net6準備新創建了一…

你所不知道的setTimeout

JavaScript提供定時執行代碼的功能&#xff0c;叫做定時器&#xff08;timer&#xff09;&#xff0c;主要由setTimeout()和setInterval()這兩個函數來完成。它們向任務隊列添加定時任務。初始接觸它的人都覺得好簡單&#xff0c;實時上真的如此么&#xff1f;這里記載下&#…

android 特效繪圖,Android繪圖機制與處理技巧——Android圖像處理之圖形特效處理...

Android變形矩陣——Matrix對于圖像的圖形變換&#xff0c;Android系統是通過矩陣來進行處理的&#xff0c;每個像素點都表達了其坐標的X、Y信息。Android的圖形變換矩陣是一個3x3的矩陣&#xff0c;如下圖所示&#xff1a;72F0CAC1-14FB-40F8-A430-8F542B09DC4E.png當使用變換…