摘要:C# 3.0 的新功能:區域變數型別推測(兼談 VB 9.0 對此語法的支援與升級問題)
C# 3.0 有不少新語法是因應 LINQ(Language INtegrated Query)而生,因此,在學習 LINQ 之前,必須先掌握 C# 3.0 新增加的幾個關鍵語法。這裡先談談區域變數型別推測的功能,亦稱為隱含型別區域變數(implicitly typed local variables)。
我們知道,在使用變數之前必須先宣告,宣告的敘述包含變數型別、名稱,也可以在宣告時設定其初始值,例如:
int i = 10;
string s = "Hello";
若某個變數可能會用來保存多種型別的值,或者在宣告時無法確定其變數型別,我們會將它宣告為 object 型別,例如:
int i = 10;
object obj;
......
obj = i; // 會發生 boxing 動作.
int k = (int) obj; // Unboxing, 需要明確轉型成 int.
現在 C# 3.0 有了區域變數型別推測的功能,於是我們可以這麼寫:
var v = 10;
int i = v; // 沒有 boxing 動作,也不需要轉型!
注意其中的 var 關鍵字,它的作用就是宣告某個區域變數為隱含型別;當編譯器看到以 var 宣告的區域變數,就會自動根據該變數所指定初始值推定其型別。 乍看之下,用 var 宣告的變數似乎跟 object 型別或早期 COM 程式設計模型中的 Variant 型別類似,實際上它們並不相同,因為 var 變數依然是強型別(strongly typed),而非動態型別。你可以利用 Reflector 工具查看上述程式碼編譯後的結果,var v = 10; 會被編譯成 int v = 10;,由此便可確認。事實上,當你在 Visual Studio 2008 的編輯器中輸入完一行宣告,該變數的型別就已經確定了。
從以上簡短的說明可得知隱含型別的兩個基本要件:
- 它必須用在區域變數(所以類別成員不可宣告為隱含型別)。
- 宣告時就必須給定初始值(編譯器方能判斷該編譯成什麼型別)。
以下是更多隱含型別的例子:
var m = 10;
var n = m; // OK! 初始值可以是已經宣告的 var 變數.
var flag = true; // bool 變數.
var numbers = new int[] {10, 20, 30, 40}; // OK! numbers 是一個整數陣列
foreach (var x in numbers) // OK! 可用在 foreach 的變數宣告(它也是區域變數)
{
Console.WriteLine(x);
}
var dogs = new List<Dog>(); // OK! dogs 是一個包含 Dog 物件的串列
var k = default(int); // OK! default 關鍵字會傳回指定型別的預設值.
注意其中宣告整數陣列的敘述甚至可以這麼寫:
var numbers = new[] {10, 20, 30, 40}; // OK! numbers 是一個整數陣列
兩者的區別在於後者是用 new[] 而不是 new int[]。這是隱含型別區域陣列(implicitly typed local array)的語法。
當你使用隱含型別來宣告陣列時,陣列的所有元素必須為同一型別,因此以下程式碼將無法通過編譯:
var mixedAry = new[] { 10, "abc" }; // Error! 編譯器無法決定該採用什麼型別.
以下是其他有關隱含型別的使用限制:
- 不能用來宣告函式的參數,也不能用於傳回值。
- 初始值不可為 null(否則編譯器如何推定其型別?)
只是懶人語法?
隱含型別的語法看起來似乎是專為懶人設計的,的確,程式設計師在撰寫程式時,可以不管三七二十一,把所有區域變數都宣告成 var -- 只要它們符合隱含型別的語法規則。但如果純粹是因為方便,想要省去思考變數該用什麼型別的時間,其實並沒有佔到什麼便宜,因為在宣告時就必須給定初始值 了,既然有初始值,寫程式的人一定知道是什麼型別了,又何必捨明確型別不用,而採用隱晦不明的 var 宣告?更何況,這樣在日後維護時反而會造成解讀程式碼的困擾。
C# 3.0 之所以新增此語法,並非鼓勵程式設計師任意使用,而是有其必要性。因為 C# 3.0 的另一個新功能--匿名型別(anonymous type)--必須依賴此語法。簡單地說,匿名型別的變數必須宣告為 var,此外別無他法。這裡暫且不討論匿名型別,因為它還牽涉到 C# 3.0 的另一個新語法:物件初始化(object initialization)。等到下次介紹完物件初始化的語法,再來看匿名型別吧。
Visual Basic 9.0 的支援與升級問題
最後要補充說明的是,VB 9.0 也支援隱含型別區域變數的語法,參考以下範例:
Dim i = 10
Dim s = "Hello"
如您所見,它就只是把 Dim x as type 後面的 as type 拿掉,再加上初始值而已。這種寫法很像是把 Option Strict 編譯選項關閉時允許的簡便寫法,例如:
Dim x = 10
x = "Hello"
Console.WriteLine(x)
不過,Visual Basic .NET 程式設計師最好開始養成習慣將 Option Strict 編譯選項設為 On,並且明確宣告變數的型別及明確轉型, 否則在把 VB 8 的專案升級到 VB 9 時,就可能會碰到一些麻煩。以前面的範例來說,當 Option Strict 編譯選項為 Off,在 Visual Studio 2005 中編譯和執行都沒有問題,在 Visual Studio 2008 則可以通過編譯,但執行到 x = "Hello" 這行時,就會因為轉型失敗而丟出 exception,因為 x 已經被編譯器指定為 Integer 型別。若 Option Strict 編譯選項為 On,則連編譯都無法通過,因為型別不相容的問題在編譯時期就會抓出來。對於平常就習慣明確宣告型別與明確轉型(而不依賴 VB 方便語法)的人來說,將來升級到 VB 9 時就不會碰到這類問題。