【轉載】C# 理解泛型

術語表

generics:泛型
type-safe:類型安全
collection: 集合
compiler:編譯器
run time:程序運行時
object: 對象
.NET library:.Net類庫
value type: 值類型
box: 裝箱
unbox: 拆箱
implicity: 隱式
explicity: 顯式
linked list: 線性鏈表
node: 結點
indexer: 索引器

簡介

Visual C# 2.0 的一個最受期待的(或許也是最讓人畏懼)的一個特性就是對于泛型的支持。這篇文章將告訴你泛型用來解決什么樣的問題,以及如何使用它們來提高你的代碼質量,還有你不必恐懼泛型的原因。

泛型是什么?

很多人覺得泛型很難理解。我相信這是因為他們通常在了解泛型是用來解決什么問題之前,就被灌輸了大量的理論和范例。結果就是你有了一個解決方案,但是卻沒有需要使用這個解決方案的問題。

這篇文章將嘗試著改變這種學習流程,我們將以一個簡單的問題作為開始:泛型是用來做什么的?答案是:沒有泛型,將會很難創建類型安全的集合。

C# 是一個類型安全的語言,類型安全允許編譯器(可信賴地)捕獲潛在的錯誤,而不是在程序運行時才發現(不可信賴地,往往發生在你將產品出售了以后!)。因此,在C#中,所有的變量都有一個定義了的類型;當你將一個對象賦值給那個變量的時候,編譯器檢查這個賦值是否正確,如果有問題,將會給出錯誤信息。

在 .Net 1.1 版本(2003)中,當你在使用集合時,這種類型安全就失效了。由.Net 類庫提供的所有關于集合的類全是用來存儲基類型(Object)的,而.Net中所有的一切都是由Object基類繼承下來的,因此所有類型都可以放到一個集合中。于是,相當于根本就沒有了類型檢測。

更糟的是,每一次你從集合中取出一個Object,你都必須將它強制轉換成正確的類型,這一轉換將對性能造成影響,并且產生冗長的代碼(如果你忘了進行轉換,將會拋出異常)。更進一步地講,如果你給集合中添加一個值類型(比如,一個整型變量),這個整型變量就被隱式地裝箱了(再一次降低了性能),而當你從集合中取出它的時候,又會進行一次顯式地拆箱(又一次性能的降低和類型轉換)。

關于裝箱、拆箱的更多內容,請訪問 陷阱4,警惕隱式的裝箱、拆箱。

創建一個簡單的線性鏈表

為了生動地感受一下這些問題,我們將創建一個盡可能簡單的線性鏈表。對于閱讀本文的那些從未創建過線性鏈表的人。你可以將線性鏈表想像成有一條鏈子栓在一起的盒子(稱作一個結點),每個盒子里包含著一些數據 和 鏈接到這個鏈子上的下一個盒子的引用(當然,除了最后一個盒子,這個盒子對于下一個盒子的引用被設置成NULL)。

為了創建我們的簡單線性鏈表,我們需要下面三個類:

1、Node 類,包含數據以及下一個Node的引用。

2、LinkedList 類,包含鏈表中的第一個Node,以及關于鏈表的任何附加信息。

3、測試程序,用于測試 LinkedList 類。

為了查看鏈接表如何運作,我們添加Objects的兩種類型到鏈表中:整型 和 Employee類型。你可以將Employee類型想象成一個包含關于公司中某一個員工所有信息的類。出于演示的目的,Employee類非常的簡單。

public class Employee{private string name;public Employee (string name){this.name = name;}public override string ToString(){return this.name;}
}

這個類僅包含一個表示員工名字的字符串類型,一個設置員工名字的構造函數,一個返回Employee名字的ToString()方法。

鏈接表本身是由很多的Node構成,這些Note,如上面所說,必須包含數據(整型 和 Employee)和鏈表中下一個Node的引用。

public class Node{Object data;Node next;public Node(Object data){this.data = data;this.next = null;}public Object Data{get { return this.data; }set { data = value; }}public Node Next{get { return this.next; }set { this.next = value; }}
}

注意構造函數將私有的數據成員設置成傳遞進來的對象,并且將 next 字段設置成null。

這個類還包括一個方法,Append,這個方法接受一個Node類型的參數,我們將把傳遞進來的Node添加到列表中的最后位置。這過程是這樣的:首先檢測當前Node的next字段,看它是不是null。如果是,那么當前Node就是最后一個Node,我們將當前Node的next屬性指向傳遞進來的新結點,這樣,我們就把新Node插入到了鏈表的尾部。

如果當前Node的next字段不是null,說明當前node不是鏈表中的最后一個node。因為next字段的類型也是node,所以我們調用next字段的Append方法(注:遞歸調用),再一次傳遞Node參數,這樣繼續下去,直到找到最后一個Node為止。

public void Append(Node newNode){if ( this.next == null ){this.next = newNode;}else{next.Append(newNode);}
}

Node 類中的 ToString() 方法也被覆蓋了,用于輸出 data 中的值,并且調用下一個 Node 的 ToString()方法(譯注:再一次遞歸調用)。

public override string ToString(){string output = data.ToString();if ( next != null ){output += ", " + next.ToString();}return output;
}

這樣,當你調用第一個Node的ToString()方法時,將打印出所有鏈表上Node的值。

LinkedList 類本身只包含對一個Node的引用,這個Node稱作 HeadNode,是鏈表中的第一個Node,初始化為null。

public class LinkedList{Node headNode = null;
}

LinkedList 類不需要構造函數(使用編譯器創建的默認構造函數),但是我們需要創建一個公共方法,Add(),這個方法把 data存儲到線性鏈表中。這個方法首先檢查headNode是不是null,如果是,它將使用data創建結點,并將這個結點作為headNode,如果不是null,它將創建一個新的包含data的結點,并調用headNode的Append方法,如下面的代碼所示:

public void Add(Object data){if ( headNode == null ){headNode = new Node(data);}else{headNode.Append(new Node(data));}
}

為了提供一點集合的感覺,我們為線性鏈表創建一個索引器。

public object this[ int index ]{get{int ctr = 0;Node node = headNode;while ( node != null  && ctr <= index ){if ( ctr == index ){return node.Data;}else{node = node.Next;}ctr++;}return null;}
}

最后,ToString()方法再一次被覆蓋,用以調用headNode的ToString()方法。

public override string ToString(){if ( this.headNode != null ){return this.headNode.ToString();}else{return string.Empty;}
}

測試線性鏈表

我們可以添加一些整型值到鏈表中進行測試:

public void Run(){LinkedList ll = new LinkedList();for ( int i = 0; i < 10; i ++ ){ll.Add(i);}Console.WriteLine(ll);Console.WriteLine("  Done. Adding employees...");
}

如果你對這段代碼進行測試,它會如預計的那樣工作:

0, 1, 2, 3, 4, 5, 6, 7, 8, 9
Done. Adding employees...

然而,因為這是一個Object類型的集合,所以你同樣可以將Employee類型添加到集合中。

ll.Add(new Employee("John"));
ll.Add(new Employee("Paul"));
ll.Add(new Employee("George"));
ll.Add(new Employee("Ringo"));
Console.WriteLine(ll);
Console.WriteLine("  Done.");

輸出的結果證實了,整型值和Employee類型都被存儲在了同一個集合中。

0, 1, 2, 3, 4, 5, 6, 7, 8, 9
? Done. Adding employees...
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, John, Paul, George, Ringo
Done.

雖然看上去這樣很方便,但是負面影響是,你失去了所有類型安全的特性。因為線性鏈表需要的是一個Object類型,每一個添加到集合中的整型值都被隱式裝箱了,如同 IL 代碼所示:

IL_000c:? box??????? [mscorlib]System.Int32
IL_0011:? callvirt?? instance void ObjectLinkedList.LinkedList::Add(object)

同樣,如果上面所說,當你從你的列表中取出項目的時候,這些整型必須被顯式地拆箱(強制轉換成整型),Employee類型必須被強制轉換成 Employee類型。

Console.WriteLine("The fourth integer is " + Convert.ToInt32(ll[3]));
Employee d = (Employee) ll[11];
Console.WriteLine("The second Employee is " + d);

這些問題的解決方案是創建一個類型安全的集合。一個 Employee 線性鏈表將不能接受 Object 類型;它只接受 Employee類的實例(或者繼承自Employee類的實例)。這樣將會是類型安全的,并且不再需要類型轉換。一個 整型的 線性鏈表,這個鏈表將不再需要裝箱和拆箱的操作(因為它只能接受整型值)。

作為示例,你將創建一個 EmployeeNode,該結點知道它的data的類型是Employee。

public class EmployeeNode {Employee employeedata;EmployeeNode employeeNext;
}

Append 方法現在接受一個 EmployeeNode 類型的參數。你同樣需要創建一個新的 EmployeeLinkedList ,這個鏈表接受一個新的 EmployeeNode:

public class EmployeeLinkedList{EmployeeNode headNode = null;
}

EmployeeLinkedList.Add()方法不再接受一個 Object,而是接受一個Employee:

public void Add(Employee data){if ( headNode == null ){headNode = new EmployeeNode(data);}else{headNode.Append(new EmployeeNode(data));}
}

類似的,索引器必須被修改成接受 EmployeeNode 類型,等等。這樣確實解決了裝箱、拆箱的問題,并且加入了類型安全的特性。你現在可以添加Employee(但不是整型)到你新的線性鏈表中了,并且當你從中取出Employee的時候,不再需要類型轉換了。

EmployeeLinkedList employees = new EmployeeLinkedList();
employees.Add(new Employee("Stephen King"));
employees.Add(new Employee("James Joyce"));
employees.Add(new Employee("William Faulkner"));
/* employees.Add(5);  // try to add an integer - won't compile */
Console.WriteLine(employees);
Employee e = employees[1];
Console.WriteLine("The second Employee is " + e);

這樣多好啊,當有一個整型試圖隱式地轉換到Employee類型時,代碼甚至連編譯器都不能通過!

但它不好的地方是:每次你需要創建一個類型安全的列表時,你都需要做很多的復制/粘貼 。一點也不夠好,一點也沒有代碼重用。同時,如果你是這個類的作者,你甚至不能提前欲知這個鏈接列表所應該接受的類型是什么,所以,你不得不將添加類型安全這一機制的工作交給類的使用者---你的用戶。

使用泛型來達到代碼重用

解決方案,如同你所猜想的那樣,就是使用泛型。通過泛型,你重新獲得了鏈接列表的   代碼通用(對于所有類型只用實現一次),而當你初始化鏈表的時候你告訴鏈表所能接受的類型。這個實現是非常簡單的,讓我們重新回到Node類:

public class Node{Object data;...

注意到 data 的類型是Object,(在EmployeeNode中,它是Employee)。我們將把它變成一個泛型(通常,由一個大寫的T代表)。我們同樣定義Node類,表示它可以被泛型化,以接受一個T類型。

public class Node <T>{T data;...

讀作:T類型的Node。T代表了當Node被初始化時,Node所接受的類型。T可以是Object,也可能是整型或者是Employee。這個在Node被初始化的時候才能確定。

注意:使用T作為標識只是一種約定俗成,你可以使用其他的字母組合來代替,比如這樣:

public class Node <UnknownType>{UnknownType data;...

通過使用T作為未知類型,next字段(下一個結點的引用)必須被聲明為T類型的Node(意思是說接受一個T類型的泛型化Node)。

??? Node<T> next;

構造函數接受一個T類型的簡單參數:

public Node(T data)
{this.data = data;this.next = null;
}

Node 類的其余部分是很簡單的,所有你需要使用Object的地方,你現在都需要使用T。LinkedList 類現在接受一個 T類型的Node,而不是一個簡單的Node作為頭結點。

public class LinkedList<T>{
??? Node<T> headNode = null;

再來一遍,轉換是很直白的。任何地方你需要使用Object的,現在改做T,任何需要使用Node的地方,現在改做 Node<T>。下面的代碼初始化了兩個鏈接表。一個是整型的。

LinkedList<int> ll = new LinkedList<int>();

另一個是Employee類型的:

LinkedList<Employee> employees = new LinkedList<Employee>();

剩下的代碼與第一個版本沒有區別,除了沒有裝箱、拆箱,而且也不可能將錯誤的類型保存到集合中。

LinkedList<int> ll = new LinkedList<int>();
for ( int i = 0; i < 10; i ++ )
{ll.Add(i);
}
Console.WriteLine(ll);
Console.WriteLine("  Done.");
LinkedList<Employee> employees = new LinkedList<Employee>();
employees.Add(new Employee("John"));
employees.Add(new Employee("Paul"));
employees.Add(new Employee("George"));
employees.Add(new Employee("Ringo"));
Console.WriteLine(employees);
Console.WriteLine("  Done.");
Console.WriteLine("The fourth integer is " + ll[3]);
Employee d = employees[1];
Console.WriteLine("The second Employee is " + d);

泛型允許你不用復制/粘貼冗長的代碼就實現類型安全的集合。而且,因為泛型是在運行時才被擴展成特殊類型。Just In Time編譯器可以在不同的實例之間共享代碼,最后,它顯著地減少了你需要編寫的代碼。

?

出 處:http://www.ondotnet.com/pub/a/dotnet/2004/05/17/liberty.html

【轉自】http://www.cnblogs.com/JimmyZhang/archive/2007/08/04/842663.html

轉載于:https://www.cnblogs.com/bad-man/p/7908007.html

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

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

相關文章

javascript 作用_JavaScript承諾如何從內到外真正發揮作用

javascript 作用One of the most important questions I faced in interviews was how promises are implemented. Since async/await is becoming more popular, you need to understand promises.我在采訪中面臨的最重要的問題之一是如何實現承諾。 由于異步/等待變得越來越流…

linux 文件理解,對linux中文件系統的理解

首先在linux系統當中一個可被掛在的數據為一個文件系統1.在安裝linux過程中我們要進行磁盤分區&#xff0c;可以分根目錄/,‘/home‘&#xff0c;‘/boot’,swap等等這些分區&#xff0c;每一個分區(’/(根目錄)‘&#xff0c;’/home‘...)就是一個文件系統。2.文件系統分配完…

編譯原理—語法分析器(Java)

遞歸下降語法分析 1. 語法成分說明 <語句塊> :: begin<語句串> end <語句串> :: <語句>{&#xff1b;<語句>} <語句> :: <賦值語句> | <循環語句> | <條件語句> <關系運算符> :: < | < | > | > | |…

老筆記整理四:字符串的完美度

今天在寵果網上發現一道題目&#xff0c;求一個字符串的完美度http://hero.pongo.cn/home/index覺得這道題很有趣就挑戰了一下&#xff0c;結果沒有在規定的1小時里面寫完&#xff08;笑&#xff09;&#xff0c;多花了10分鐘終于做出來了。題目是這樣的&#xff1a;我們要給每…

nlp構建_使用NLP構建自殺性推文分類器

nlp構建Over the years, suicide has been one of the major causes of death worldwide, According to Wikipedia, Suicide resulted in 828,000 global deaths in 2015, an increase from 712,000 deaths in 1990. This makes suicide the 10th leading cause of death world…

域名跳轉

案例&#xff1a;當訪問lsx.com網站&#xff0c;是我最早論壇的域名。回車之后會自動跳轉到lshx.com。 為什么藥lsx跳轉到lshx.com呢&#xff1f; 為了統一品牌。建議換成了lshx.com。所有之前的lsx.com就不要用了&#xff0c;就讓它跳轉到lshx.com。是因為之前lsx.com上有很多…

Elastic Stack 安裝

Elastic Stack 是一套支持數據采集、存儲、分析、并可視化全面的分析工具&#xff0c;簡稱 ELK&#xff08;Elasticsearch&#xff0c;Logstash&#xff0c;Kibana&#xff09;的縮寫。 安裝Elastic Stack 時&#xff0c;必須相關組件使用相同的版本&#xff0c;例如&#xff1…

區塊鏈去中心化分布式_為什么漸進式去中心化是區塊鏈的最大希望

區塊鏈去中心化分布式by Arthur Camara通過亞瑟卡馬拉(Arthur Camara) 為什么漸進式去中心化是區塊鏈的最大希望 (Why Progressive Decentralization is blockchain’s best hope) 不變性是區塊鏈的最大優勢和最大障礙。 逐步分權可能是答案。 (Immutability is blockchain’s…

編譯原理—語義分析(Java)

遞歸下降語法制導翻譯 實現含多條簡單賦值語句的簡化語言的語義分析和中間代碼生成。 測試樣例 begin a:2; b:4; c:c-1; area:3.14*a*a; s:2*3.1416*r*(hr); end #詞法分析 public class analyzer {public static List<String> llistnew ArrayList<>();static …

linux問題總結

linux問題總結 編寫后臺進程的管理腳本&#xff0c;使用service deamon-name stop的時候&#xff0c;出現如下提示&#xff1a;/sbin/service: line 66: 23299 Terminated env -i LANG"$LANG" PATH"$PATH" TERM"$TERM" "${SERVICEDIR}/${SE…

linux vi行尾總是顯示顏色,【轉載】Linux 下使用 vi 沒有顏色的解決辦法

vi 是沒有顏色的&#xff0c;vim 是有顏色的。我們可以通過 rpm -qa |grep vim 看看系統中是否安裝了下面 3 個 rpm 包&#xff0c;如果有就是安裝了 vim 。[rootBetty ~]# rpm -qa |grep vimvim-minimal-7.0.109-7.el5vim-enhanced-7.0.109-7.el5vim-common-7.0.109-7.el5如果…

時間序列分析 lstm_LSTM —時間序列分析

時間序列分析 lstmNeural networks can be a hard concept to wrap your head around. I think this is mostly due to the fact that they can be used for so many different things such as classification, identification or just simply regression.神經網絡可能是一個難…

關于計算圓周率PI的經典程序

短短幾行代碼&#xff0c;卻也可圈可點。如把變量s放在PI表達式中&#xff0c;還有正負值的處理&#xff0c;都堪稱經典。尤其是處處考慮執行效率的思想令人敬佩。 /* pi/41-1/31/5-1/71/9-…… */ #include <stdio.h> int main(){ int s1; float pi0.,n1.,…

華為產品技術學習筆記之路由原理(一)

路由器&#xff1a;路由器是一種典型的網絡連接設備&#xff0c;用來進行路由選擇和報文轉發。路由器與它直接相連的網絡的跳數為0&#xff0c;通過一臺路由器可達的網絡的跳數為1.路由協議&#xff1a;路由器之間維護路由表的規則&#xff0c;用以發現路由&#xff0c;生成路由…

Linux網絡配置:設置IP地址、網關DNS、主機名

查看網絡信息 1、ifconfig eth0 2、ifconfig -a 3、ip add 設置主機名需改配置文件&#xff1a; /etc/hosts /etc/sysconfig/network vim /etc/sysconfig/network NETWORKINGyes NETWORKING_IPV6no HOSTNAMEwendyhost Linux配置網絡 方法一&#xff1a; 1、使用setup命令進入如…

編譯原理—小型(簡化)高級語言分析器前端(Java)

實現一個一遍掃描的編譯前端&#xff0c;將簡化高級語言的部分語法成分&#xff08;含賦值語句、分支語句、循環語句等&#xff09;翻譯成四元式&#xff08;或三地址代碼&#xff09;&#xff0c;還要求有合理的語法出錯報錯和錯誤恢復功能。 測試樣例 beginwhile a<b do…

linux boot菜單列表,Bootstrap 下拉菜單(Dropdowns)簡介

Bootstrap 下拉菜單是可切換的&#xff0c;是以列表格式顯示鏈接的上下文菜單。這可以通過與 下拉菜單(Dropdown) JavaScript 插件 的互動來實現。如需使用下拉菜單&#xff0c;只需要在 class .dropdown 內加上下拉菜單即可。下面的實例演示了基本的下拉菜單&#xff1a;實例主…

dynamodb管理ttl_如何使用DynamoDB TTL和Lambda安排臨時任務

dynamodb管理ttlby Yan Cui崔燕 如何使用DynamoDB TTL和Lambda安排臨時任務 (How to schedule ad-hoc tasks with DynamoDB TTL and Lambda) CloudWatch Events let you easily create cron jobs with Lambda. However, it’s not designed for running lots of ad-hoc tasks,…

5g創業的構想_數據科學項目的五個具體構想

5g創業的構想Do you want to enter the data science world? Congratulations! That’s (still) the right choice.您想進入數據科學世界嗎&#xff1f; 恭喜你&#xff01; 那(仍然)是正確的選擇。 The market currently gets tougher. So, you must be mentally prepared f…

Microsoft Windows Phone 7 Toolkit Silverlight SDK XNA Game Studio 4.0 開發工具套件正式版下載...

Windows Phone 7開發工具套件包括Visual Studio 2010 Express for Windows Phone、Windows Phone模擬器、Expression Blend 4 for Windows Phone、XNA Game Studio 4.0和新增加的必應地圖SDK。 英文版的光盤鏡像&#xff1a;點擊下載 文檔中心&#xff1a;Windows Phone develo…