[C#] 明確型別宣告 (Explicit Type Declaration) 與隱含型別宣告 (Implicit Type Declaration)

C# 的 var 是極度的好物,它可以幫你解決長名稱或容易弄錯的型別的自動轉換,但如果用 var 只是拿來規避重構所產生的物件設計問題,那其實只是鴕鳥心態而已。

本文的誔生只是因為我看到了有些人對為什麼要使用 var 有著很奇怪的解釋而來,所以我不會對 var 有太多很細節的描述,如果想快快樂樂的學習 var 的話,我推薦這篇文章:
[.NET]快快樂樂學LINQ系列前哨戰-var與匿名型別

為什麼會有 var?

要了解它真正的用意,就要了解為什麼 C# 設計者要設計這個指令,當然你也可以不去了解,但慢慢的你就會發現不知道為什麼的時候,就像在浮砂上築高塔一樣,總會覺得不夠踏實。

var 是在 C# 3.0 時產生的一個指令,最初的目的是為了要解兩個問題,一個是型別名稱太長,例如像是:

Microsoft.Office.Inteop.Excel.Sheet sheet = new Microsoft.Office.Interop.Excel.Sheel();

這麼長的名稱雖然可以用 using Microsoft.Office.Interop.Excel  取代,但若程式的引用的命名空間太多時,就會有滿到溢出來的 using 充斥在你的 C# 檔案開頭,這時候 var 就可以幫你解決這個問題了,因為它是編譯時期的型別推論 (compiler time type inference),可幫助開發人員減少這部份的負擔,如果你覺得這個例子太爛,那我再舉一個:

Tuple<int, int, int, int, int, int, int, int, int, int, int, int, int, int> GenerateReportFunc();

如果用明顯的型別宣告,我看開發人員可能真的要跳樓了吧,這時候 var 的強項就很明顯了,只要寫個 var report = GenerateReportFunc();,就能優雅的解決問題。

不用懷疑這樣的型態,當然一定會有人說為什麼不宣告個 class 來放資料,但是如果這種只用一次,若太多的一次性類別 (one-time class) 充斥在專案各個角落,我想你應該也不會覺得好受,因為光是維護成本可能就遠高於使用 Tuple 了,更何況現在 C# 7.0 又支援了 named tuple (你可以對 tuple 的每個欄位給名稱)

第二種情況就是引用來源的型別不定性,這種情況容易發生在使用別人的元件時。

我要定義一下這裡說的別人的元件,是指團隊無法對該元件做任何原始碼上異動的元件,通常是指不同公司開發的,或是舊到連原始碼都沒有的元件。
如果是同一個團隊可異動原始碼的,那是設計問題,應該重構,而不是用 var 去規避,那是鴕鳥心態。

除了 LINQ (IQueryable IEnumerable 外),我記得最經典的算是 NPOI 1.x->2.x 這一段,因為原本 1.x 用的是實作型別,例如 Row (類別),但到了 2.x,它反而變成了 IRow (介面):


// NPOI 1.x
Row row = sheet.GetRow(1);
// NPOI 2.x
IRow row = sheet.GetRow(i);

像這種原本使用實作但後面突然改成抽象時,使用明顯型別宣告的程式會出現編譯錯誤,因為編譯器不會知道傳來的抽象確實就是你指定的物件類別,因此會送你 Compile Error。這時你有兩個解決方法:

// solution 1: explicit type cast
Row row = (Row)sheet.GetRow(1);
// solution 2: compile-time type inference.
var row = sheet.GetRow(1);

使用顯式型別轉換 (Explicit Type Cast) 可以解決問題,但會增加 InvalidCastException 的風險,因為你不會知道它下次會不會變成 Row2 的實作,這時使用編譯時期型別推論的優點的浮現出來了。

為什麼要推薦使用 var?

原因很簡單,因為它可以幫你解決在型態不明顯、型別名稱過長或是型態可能會異動的情境,而且可以統一團隊的 coding style,也減少一些初階的開發人員在型別上的使用錯誤。

另外也有一些原因是要求一定要使用 var 的,例如前面有提過的 named tuple。

最最最最最重要的一點是,它不會對效能產生任何影響,使用明確宣告和使用 var 所產生的 IL Code 是相同的。

不能使用 var 的情境

var 真的是極度好物,但是有幾個地方不能使用或不宜使用。

  1. 無法事先給予初值的情況,因為這樣無法讓編譯器推論它應有的型態。
  2. 值域相當接近的情況,例如 float, double int, long,若對值的正確性很敏感的話,最好用明確宣告。
  3. 已經要求要有明確的型別宣告時,這時就不宜使用 var
  4. 若要求型別是抽象 (abstract, 例如 interface),但是來源傳回的是具體類別 (concrete class) 時,不宜使用 var,它會將型別推斷成具體類別,會喪失抽象的優勢。

Reference:

https://dotblogs.com.tw/hatelove/2012/04/20/var-and-anonymous-types-introduction

https://stackoverflow.com/questions/356846/will-using-var-affect-performance