第 88 講:C# 3 之隱式變量類型聲明
歡迎來到 C# 3!今天我們來看第一個(gè)新語(yǔ)法:隱式變量聲明(Implicit-typed Variables)。
Part 1 var
關(guān)鍵字
考慮一種情況。如果我現(xiàn)在有一個(gè)巨長(zhǎng)無比的數(shù)據(jù)類型 Dictionary<int, Pair<string, IReadOnlyDictionary<string, decimal>>>
。我們按照基本拆分的規(guī)則來看,這個(gè)可以表達(dá)一個(gè)學(xué)生的基本信息。Dictionary<int, ..>
類型的 int
作為鍵,搜索和唯一確定學(xué)生的學(xué)號(hào);而值則是 Pair<string, IReadOnlyDictionary<string, decimal>>
類型。Pair<,>
類型是隨便寫的,它用來表示一個(gè)數(shù)據(jù)對(duì),因?yàn)閱蝹€(gè)數(shù)據(jù)沒辦法存到這個(gè)字典里面,所以只能用一個(gè)單獨(dú)的數(shù)據(jù)類型表達(dá);接著,Pair
里帶有兩個(gè)泛型參數(shù),第一個(gè)實(shí)際類型為 string
想表示學(xué)生的名字,而右邊的 IReadOnlyDictionary<string, decimal>
則是一個(gè)字典,表示和存儲(chǔ)這個(gè)學(xué)生的各個(gè)科目的學(xué)習(xí)成績(jī)。string
是表示鍵,搜索和表示科目名,而 decimal
則對(duì)應(yīng)值,表示當(dāng)前科目的成績(jī)分?jǐn)?shù)。
可以看到,如果你這么實(shí)例化的話:
光一個(gè)實(shí)例化就得寫得巨長(zhǎng)。于是,我們可以借助 C# 2 的語(yǔ)法“類型別名”來完成:
或者:
不過,這么做也不合理的地方在于,這種類型沒必要單獨(dú)寫一個(gè) using
。要遇到一堆這樣的東西,全部寫在 using
指令上去,一來是很不方便,二來是太長(zhǎng)了。放在 using
指令上還需要你手寫和寫全整個(gè)數(shù)據(jù)類型的所有位置上的類型的全名。
C# 3 引入了一個(gè)新的概念:隱式變量類型。我們?cè)谥皩W(xué)到的所有語(yǔ)法里,變量左側(cè)必須配上一個(gè)名字。這個(gè)名字一定是類型的名稱,不管是帶命名空間的,還是直接只寫名字,這種寫法我們都稱為顯式變量類型(Explicit-typed Variable)。有些時(shí)候名字比較長(zhǎng),或者是帶泛型的時(shí),泛型參數(shù)巨長(zhǎng)的時(shí)候,這么寫起來都不方便。
C# 3 使用關(guān)鍵字 var
來表示數(shù)據(jù)類型,于是你只需要在 new
后面寫一次變量的實(shí)際類型的實(shí)例化過程,就可以了:
在變量左側(cè)使用 var
代替掉原本完全寫出的顯式數(shù)據(jù)類型,這個(gè) var
就顯得非常方便了。
Part 2 用法
我們?cè)囍鴣肀硎竞褪褂?var
吧!
2-1 舉例
這個(gè)是排序的代碼。我們這次請(qǐng)看第 3、5 和第 9 行,我們都用到了 var
關(guān)鍵字??梢园l(fā)現(xiàn),var
的用法其實(shí)就是代替變量的數(shù)據(jù)類型,一勞永逸地使用 var
即可。
2-2 會(huì)不會(huì)降低了可讀性?
不過有人可能會(huì)問我,這樣不就降低了可讀性?我使用 var
看似是好事情,但此時(shí)變量 i
、j
和 temp
變量的實(shí)際類型就不夠清楚了。這樣說不定會(huì)降低可讀性。不過我要告訴你的是,可讀性其實(shí)基本上沒有任何的降低,只是你不習(xí)慣。為什么我這么說呢?你仔細(xì)看看這些變量,它的實(shí)際類型其實(shí)我們并不關(guān)心。拿 i
和 j
來看,難道你還在意它的數(shù)據(jù)類型具體是什么么?它是循環(huán)變量,我只需要知道它能 ++
操作,它能當(dāng)成索引放在數(shù)組的中括號(hào)里去獲取元素不就行了?而下面的 temp
也是如此。我在操作里僅用到了交換,實(shí)際上它是啥類型我們并不關(guān)心。它是 T
,而具體被啥類型替代,這些我們完全都不關(guān)心。
可能你會(huì)覺得,T
只打一個(gè)字符,而 var
卻要打三個(gè)字符出來,這不是反而復(fù)雜了?可我想說,第一,泛型參數(shù)用 var
完全是出于你熟悉了 var
語(yǔ)法后的習(xí)慣問題。你看到 var
既然廣泛被用上了,到處臨時(shí)變量都用 var
關(guān)鍵字代替了之后,突然在代碼里用了一個(gè)顯式數(shù)據(jù)類型,反而會(huì)覺得不統(tǒng)一,強(qiáng)迫癥患者表示也非常不舒服。第二是,真的時(shí)候,代替 T
這種少于三個(gè)字符的單詞的情況也不多。所有我們需要替代的數(shù)據(jù)類型,要么是關(guān)鍵字表示的(什么 sbyte
啊什么的),要么就是自己定義的數(shù)據(jù)類型。我們定義的數(shù)據(jù)類型很難取名只取只需要一個(gè)字符和兩個(gè)字符就可以的類型名稱。使用 var
再怎么說都比它們打起來方便。
真要抬杠的話,var
在少數(shù)情況也確實(shí)不及顯式數(shù)據(jù)類型,但沒有必要爭(zhēng)論這些細(xì)節(jié)上的問題,因?yàn)槲覀冃枰氖窃跁鴮懘a的時(shí)候要做到執(zhí)行效率和可讀性里找到折衷的方案,那么 var
確實(shí)達(dá)到了我們需求和想要的目的——替代復(fù)雜類型名稱。
Part 3 不能使用 var
聲明變量類型的情況
下面我們介紹一些關(guān)于 var
不能用的情況。
3-1 不能將 var
用于成員類型聲明、參數(shù)和返回值上
是的??紤]一種情況,假設(shè)我在類型聲明里用到了字段,是 var
類型的話:
這樣合理嗎?其實(shí)并不合理。我們要的目的是替代復(fù)雜類型名稱,但如果我們這里代替了的話,就會(huì)出現(xiàn)剛才說過的降低代碼可讀性的問題。這個(gè)字段是我們確確實(shí)實(shí)隨時(shí)隨地都可以用到的東西,而只有臨時(shí)變量只有小范圍才用得到,所以,超過臨時(shí)變量的使用范圍的話,代碼可讀性就會(huì)出現(xiàn)實(shí)打?qū)嵉亟档?。所以,C# 3 不允許我們將 var
用在可能會(huì)重復(fù)使用的地方上。
當(dāng)然,返回值和參數(shù)也都是如此。返回值不多說,但參數(shù)呢?因?yàn)閰?shù)是必須規(guī)定起來才能使用的,它也相當(dāng)于變量。但是問題就在于,參數(shù)的數(shù)值是從外部傳入的,而在方法里我們是無從知曉它的數(shù)據(jù)類型的,因此在定義和聲明方法的時(shí)候,參數(shù)的具體類型必須我們提前給出來。那么用 var
呢?你就不清楚是啥類型了,自然而然就是一種錯(cuò)誤使用了。
所以,總的來說,var
只用來表示一個(gè)臨時(shí)變量的類型,作為一種代替方案。
3-2 const
關(guān)鍵字不能和 var
一起用
C# 早期定義了 const
關(guān)鍵字,可以用在字段上表示字段是編譯前就得到的常量數(shù)據(jù)。而 const
實(shí)際上也可以修飾臨時(shí)變量,表示臨時(shí)使用的常量信息和數(shù)據(jù):
const
修飾符后跟的類型名稱用上 var
,即:
你可能會(huì)問,為什么???因?yàn)榭勺x性唄。一旦 const var
放在一起了,由于它是編譯前常量,因此它的作用會(huì)比正常的變量來說要大一些(不然,你為啥要修飾 const
呢?)。所以,這種情況下是有可能降低可讀性。
3-3 多變量同語(yǔ)句聲明的時(shí)候不能用 var
多變量的同語(yǔ)句聲明的語(yǔ)句是使用逗號(hào)分隔的方式:
var
替代。因?yàn)槭褂?var
我們雖然可以按照從邏輯上的數(shù)據(jù)類型兼容的原則發(fā)現(xiàn),多變量定義放在同一個(gè)語(yǔ)句里的時(shí)候,必須要確保這些變量的數(shù)據(jù)類型全部要一樣,才能寫成“變量類型 變量1 = 數(shù)值, 變量2 = 數(shù)值, ...”的格式。可問題就出在,現(xiàn)在按字面量來看,1 是 int
類型的,而 1.2 則是 double
類型的。那么,這個(gè) var
是表示啥類型呢?這就不一定了。這 a
和 b
定義語(yǔ)句顯然不允許同行,類型上就不匹配。
因此,C# 不允許同行多變量定義在同一個(gè)語(yǔ)句里面的時(shí)候,是不能用 var
的,否則會(huì)混淆編譯器的處理過程。
3-4 委托類型不能使用 var
這是一個(gè)奇怪的現(xiàn)象,也是可以說得通的現(xiàn)象。
考慮這個(gè)代碼。f
應(yīng)該表示什么?Console.WriteLine
是靜態(tài)方法組,表示執(zhí)行 Console.WriteLine
這個(gè)方法。但問題就在于,這個(gè)方法是有重載的,也就意味著參數(shù)類型我們無法從方法組里知道。因此,委托類型不允許使用 var
來賦值。
你必須改成這樣:
這樣,編譯器才知道,你這里調(diào)用的是 Console.WriteLine
方法里,傳入一個(gè) int
類型當(dāng)參數(shù)的那個(gè)重載版本。
3-5 聲明 var
的時(shí)候必須初始化
可以看到,i
在稍后被賦值了 20??蓡栴}就出現(xiàn)在編譯。因?yàn)榇a執(zhí)行和編譯看的是逐行的內(nèi)容,而 i
肯定不能因?yàn)槲液竺娴馁x值而反推前面代碼給出的 i
的數(shù)據(jù)類型,至少 C# 語(yǔ)法的嚴(yán)謹(jǐn)性不允許我們這樣做。因此,var
在使用的時(shí)候必須確保變量有賦值部分。如果變量沒有賦值部分,只是一個(gè)純變量定義的話,那么 var
是不能用在這種情況上的。
Part 4 是否選用 var
的抉擇
那么,啥時(shí)候用 var
合適呢?可以從前面的文字里看出,var
只要用在臨時(shí)變量上的話,隨便你啥時(shí)候定義類型,都可以用 var
。但,可以用不代表必須這么用。所以選取 var
作為類型聲明還是需要我們抉擇一下的。
我這里做一個(gè)建議。我在寫代碼的時(shí)候,我會(huì)遵循這個(gè)規(guī)則去書寫代碼:
如果是內(nèi)置類型具有關(guān)鍵字別名的,那么就寫關(guān)鍵字寫法,比如
int
、float
、string
等等;如果是枚舉類型,當(dāng)且僅當(dāng)它必須配合
const
修飾符的時(shí)候?qū)懭?,其它的時(shí)候都寫var
;其它任何臨時(shí)變量定義寫類型的時(shí)候,全部寫
var
。
這是我認(rèn)為最合適的書寫習(xí)慣。
對(duì)沒錯(cuò),枚舉類型是可以用
const
修飾的。因?yàn)?const
修飾的是有編譯前常量的數(shù)據(jù)類型,而枚舉類型具有特征數(shù)值類型一個(gè)概念,因此枚舉類型基本上可以認(rèn)為是整數(shù)類型的命名數(shù)據(jù)類型,即相當(dāng)于給整數(shù)的數(shù)值都取了一個(gè)名字。那么本質(zhì)也是整數(shù),所以整數(shù)是有常量一說,自然枚舉類型也有常量一說了。
下面我們還是拿排序的例子來展示我使用 var
的情況。
var
var
var
只可能是 int
。因?yàn)?var
只代替掉的是匹配的賦值方的表達(dá)式的整體類型。0 是 int
字面量,所以它最合適的類型是 int