C# in Depth-类型系统的特征
2.2 類型系統(tǒng)的特征
類型系統(tǒng)被分為強(qiáng)/弱、安全/不安全、靜態(tài)/動態(tài)以及其他一些讓人更不好懂的說法。
由于不同的人經(jīng)常用不同的術(shù)語來指代差別不是太大的兩種東西,所以很容易產(chǎn)生溝通障礙。
本節(jié)只適用于安全代碼,如果只考慮安全代碼,那么類型系統(tǒng)的各種特征會變得更容易描述和理解。
2.2.1 C#在類型系統(tǒng)世界中的位置
C# 1的類型系統(tǒng)是靜態(tài)的、顯式的和安全的
強(qiáng)類型存在多種不同的定義。
在某些強(qiáng)類型定義中,要求禁止任何形式的轉(zhuǎn)換,不管是顯式還是隱式轉(zhuǎn)換,這明顯會使C#失去資格。但在另一些定義中,卻相當(dāng)接近甚至等同于靜態(tài)類型,這會使C#獲得資格。
大多數(shù)文章和書籍都將C#描述成強(qiáng)類型的語言,但最終的意思實際都是指它是一種靜態(tài)類型的語言。現(xiàn)在,讓我們依次闡述上述結(jié)論中的每一個術(shù)語。
1. 靜態(tài)類型和動態(tài)類型
C#是靜態(tài)類型
每個變量都有一個特定的類型,而且該類型在編譯時是已知的。只有該類型已知的操作才是允許的,這一點(diǎn)由編譯器強(qiáng)制生效。
來看下面這個強(qiáng)制生效的例子:
Object o = "hello"; Console.WriteLine(o.Length);o 的值是一個字符串,同時 string 類型有一個?Length 屬性。但是編譯器只把 o 看做 object 類型。
如果想訪問 Length 屬性,必須讓編譯器知道 o 的值實際是一個字符串:
Object o = "hello"; Console.WriteLine((string)o.Length);接下來編譯器會查找 System.String 的 Length 屬性。以此來驗證調(diào)用是否正確,生成適當(dāng)?shù)腎L,并計算出整個表達(dá)式的類型。
表達(dá)式的編譯時類型仍然為靜態(tài)類型,因此我們可以說,“ o的靜態(tài)類型為 System.Object ”。
假如C#是動態(tài)類型
動態(tài)類型的實質(zhì)是變量中含有值,但那些值并不限于特定的類型,所以編譯器不能執(zhí)行相同形式的檢查,而是試圖采取一種合適的方式來理解引用值的給定表達(dá)式。
假定C# 1是動態(tài)類型的,就可以做下面的事情:
o = "hello"; Console.WriteLine(o.Length); o = new string[] {"hi", "there"}; Console.WriteLine(o.Length);通過在執(zhí)行時動態(tài)檢查類型,最終會調(diào)用兩個完全無關(guān)的 Length 屬性—— String.Length和Array.Length 。
動態(tài)類型具有不同的級別
有的語言允許在你希望的任何地方指定類型,除了在賦值時指定。指定的類型可能仍被當(dāng)作動態(tài)類型處理,但在其他地方仍然使用沒有指定類型的變量。
盡管多次聲明這是C# 1,但直到C# 3時它還是一門完全靜態(tài)的語言。C#?4引入了動態(tài)類型,然而大多數(shù)C# 4應(yīng)用程序中的大部分代碼仍然是靜態(tài)類型的。
2. 顯式類型和隱式類型
顯式類型和隱式類型的區(qū)別只有在靜態(tài)類型的語言中才有意義。
對于顯式類型來說,每個變量的類型都必須在聲明中顯式指明。隱式類型則允許編譯器根據(jù)變量的用途來推斷變量的類型,語言可以推斷出變量的類型是用于初始賦值的那個表達(dá)式的類型。
假設(shè)一個語言,它用關(guān)鍵字 var 告訴編譯器進(jìn)行類型推斷。表2-1左邊一列的代碼在C# 1中是不允許的,但是右邊一列是等價的有效代碼。
顯示/隱式類型只能是靜態(tài)類型
無論隱式類型還是顯式類型,變量的類型在編譯時都是已知的。即使在代碼中沒有顯式地聲明。在動態(tài)類型的情況下,變量根本沒有一個類型可供聲明或推斷。
3. 類型安全與類型不安全
有的語言允許做一些非常“不正當(dāng)”的事情。但在合適的時候,其功能可能會很強(qiáng)大。
但是所謂“合適的時候”實際很少能夠遇到。如使用不當(dāng),反而極有可能“搬起石頭砸自己的腳”。濫用類型系統(tǒng)就屬于這種情況。
使用一些非正當(dāng)?shù)姆椒?#xff0c;可以使語言將一種類型的值當(dāng)作另一種完全不同的類型的值,同時不必進(jìn)行任何轉(zhuǎn)換。
C#編譯器和CLR都會檢查類型安全
在完全無關(guān)的結(jié)構(gòu)(struct)之間進(jìn)行強(qiáng)制類型轉(zhuǎn)換,很容易造成嚴(yán)重的后果。C#中雖然可以進(jìn)行大量轉(zhuǎn)換,但不能說一種類型的數(shù)據(jù)是全然不同的另一種類型的數(shù)據(jù)。
可以試著添加一個強(qiáng)制類型轉(zhuǎn)換,為編譯器提供這種額外的(和不正確的)信息。但是假如編譯器發(fā)現(xiàn)這種轉(zhuǎn)換實際是不可能的,就會觸發(fā)一個編譯時錯誤。如果理論上允許,但在執(zhí)行時發(fā)現(xiàn)不正確,CLR也會拋出一個異常。
2.2.2 C# 1 的類型系統(tǒng)何時不夠用
在兩種常見的情況下,你可能想向方法的調(diào)用者揭示更多的信息,或者想強(qiáng)迫調(diào)用者對它們在參數(shù)值中提供的內(nèi)容進(jìn)行限制。
第一種情況涉及集合,第二種情況涉及繼承和覆蓋方法或?qū)崿F(xiàn)接口。我們將依次進(jìn)行討論。
1. 集合,強(qiáng)和弱
.NET 1.1內(nèi)建了以下3種集合類型:
①數(shù)組——強(qiáng)類型——內(nèi)建到語言和運(yùn)行時中。
②System.Collections 命名空間中的弱類型集合。
③System.Collections.Specialized 命名空間中的強(qiáng)類型集合。
數(shù)組是強(qiáng)類型的,所以在編譯時不可能將 string[] 的一個元素設(shè)置成一個 FileStream 。
協(xié)變
如果引用類型的數(shù)組支持協(xié)變,只要元素的類型之間允許這樣的轉(zhuǎn)換,就能隱式將一種數(shù)組類型轉(zhuǎn)換成另一種類型。執(zhí)行時會進(jìn)行檢查,以確保類型有誤的引用不會被存儲下來,如代碼清單2-3所示。
//應(yīng)用協(xié)變式轉(zhuǎn)換 string[] strings = new string[5]; object[] objects=strings; //試圖存儲一個按鈕引用 objects[0] = new Button();運(yùn)行代碼清單2-3,會拋出一個 ArrayTypeMismatchException 異常 。這是由于從string[] 轉(zhuǎn)換成 object[] 會返回原始引用,無論 strings 還是 objects 都引用同一個數(shù)組。
數(shù)組本身“知道”它是一個字符串?dāng)?shù)組,所以會拒絕存儲對非字符串的引用。數(shù)組協(xié)變有時會派上用場,但代價是一些類型安全性在執(zhí)行時才能實現(xiàn),而不能在編譯時實現(xiàn)。
弱類型的集合
寫一個 ArrayList 方法時,沒有辦法保證在編譯時調(diào)用者會傳入一個字符串列表。
雖然只要將列表的每個元素都強(qiáng)制轉(zhuǎn)換為string ,運(yùn)行時的類型安全性就會幫你強(qiáng)制使這個限制生效。但是,這樣不會獲得編譯時的類型安全性。
同樣,如果返回一個 ArrayList ,可以讓他它只包含字符串。但是調(diào)用者必須相信你說的是實話,而且在訪問列表元素時必須插入強(qiáng)制類型轉(zhuǎn)換。
強(qiáng)類型的集合
如 StringCollection ,這些集合提供了一個強(qiáng)類型的API。所以,如果接受一個 StringCollection 作為參數(shù)或返回值,可以肯定它只包含 string 。另外,在取回集合的元素時,不需要進(jìn)行強(qiáng)制類型轉(zhuǎn)換。
這聽起來似乎很理想,但有兩個問題。
①它實現(xiàn)了 IList ,所以仍然可以為它添加非字符串(的對象),雖然運(yùn)行時會失敗。
②它只能處理字符串。還有一些專門的集合,但它們包括的范圍不是很大。
例如,CollectionBase 類型,可以用它構(gòu)建你自己的強(qiáng)類型集合,但那意味著要為每種元素類型都創(chuàng)建一個新集合,所以同樣不理想。
接著看看覆蓋方法和實現(xiàn)接口時發(fā)生的問題,它們和協(xié)變的概念有關(guān)。
2. 缺乏協(xié)變的返回類型
ICloneable 是框架中最簡單的接口之一。它只有一個 Clone 方法,該方法返回調(diào)用方法的那個對象的一個副本。先看看 Clone 方法的簽名:
object Clone()這意味著它需要返回同類型的一個對象,或至少兼容類型的一個對象,具體含義要取決于類型。
返回類型的協(xié)變性
用一個覆蓋方法的簽名更準(zhǔn)確地描述該方法實際的返回值,應(yīng)該講得通。比如在 Person類中,像下面這樣實現(xiàn) ICloneable 接口:
public Person Clone()這應(yīng)該破壞不了任何東西,代碼期待的舊的對象仍然能夠正常工作。這個特性稱為返回類型的協(xié)變性。
但接口實現(xiàn)和方法覆蓋不支持這一特性,正常的解決方法是使用顯式接口實現(xiàn)來獲得預(yù)期的效果。
public Person Clone() {[Implementation goes here] } object IColoneable.Clone()//顯式實現(xiàn)接口 {return Clone();//調(diào)用非接口方法 }這樣一來,任何代碼為一個表達(dá)式調(diào)用 Clone() 時,如果編譯器知道這個表達(dá)式的類型是Person ,就會調(diào)用上面的方法,如果表達(dá)式的類型只是 ICloneable ,就會調(diào)用下面的方法。這雖然可行,但真的太別扭了。
參數(shù)的逆變性
假定一個接口方法或一個虛方法,其簽名是 void Process(string x) ,那么在實現(xiàn)或者覆蓋這個方法時,使用一個放寬了限制的簽名應(yīng)該是合乎邏輯的,如 void Process(object x) 。
這稱為參數(shù)類型的逆變性。和返回類型的協(xié)變性一樣,參數(shù)類型的逆變性也是不支持的。
對于接口,解決方案是一樣的,同樣都是進(jìn)行顯式接口實現(xiàn)。對于虛方法,解決方案則是進(jìn)行普通的方法重載。雖然不是什么大問題,卻著實煩人。
當(dāng)然,C# 1的開發(fā)者被這些問題折磨了很長時間,Java開發(fā)者的情況類似,而且他們被折磨了更長時間。
雖然編譯時的類型安全性總的來說是非常出色的一個特性,但許多bug實際上是由于在集合中放置了錯誤類型的元素造成的。
C# 2在這方面也不是完美的,但它確實有了相當(dāng)大的改進(jìn)。C# 4的改進(jìn)更大,但還是不包括返回類型協(xié)變和參數(shù)逆變。
2.2.3 類型系統(tǒng)特征總結(jié)
本節(jié)描述了不同類型系統(tǒng)的一些差異,并具體描述了C# 1的特征:
①C# 1是靜態(tài)類型的——編譯器知道你能使用哪些成員;
②C# 1是顯式的——必須告訴編譯器變量具有什么類型;
③C# 1是安全的——除非存在真實的轉(zhuǎn)換關(guān)系,否則不能將一種類型當(dāng)做另一種類型;
④靜態(tài)類型仍然不允許一個集合成為強(qiáng)類型的“字符串列表”或者“整數(shù)列表”,除非針對不同的元素使用大量的重復(fù)代碼;
⑤方法覆蓋和接口實現(xiàn)不允許協(xié)變性/逆變性。
下一節(jié)暫停討論C#類型系統(tǒng)的高級特征。相反,讓我們討論一下最基本的概念:結(jié)構(gòu)和類的差異。
轉(zhuǎn)載于:https://www.cnblogs.com/errornull/p/10019993.html
與50位技術(shù)專家面對面20年技術(shù)見證,附贈技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的C# in Depth-类型系统的特征的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 虚拟机centos7 识别不出网卡的解决
- 下一篇: 链式前项星(模板)