在 C#/.NET 裡,格式化字串的機制多種多樣:從單純把物件串接起來,到呼叫被覆寫的 object.ToString() 方法,再到使用 ICustomFormatter(本練習不涵蓋)都有。
最常見的兩種字串格式化機制是字串內插和 String.Format()。如果情況比較複雜,例如牽涉到多行,也可以用 StringBuilder(cross-ref-tba)類別來組出字串。
ToString()
所有類別和結構都繼承自 System.Object(),而它有一個 ToString() 方法。例如 new DateTime(2019, 5, 23).ToString() 會輸出「05/23/2019 00:00:00」(在採用美國文化的執行緒上,見下文)。在某些情況下,例如字串串接,這個預設的 ToString() 方法可能會被隱式呼叫,"" + new DateTime(2019, 5, 23) 會得到同樣的結果。
除了預設的 ToString() 方法之外,格式化會造成困擾的型別還會提供多載,可以接受格式字串甚至格式提供者。在 BCL(Base Class Library,基底類別庫)中,主要有數字、日期、列舉和 GUID 這幾類。
String.Format() 接受一個字串(文件中稱為_複合格式_),其中包含固定文字和預留位置(文件中稱為_格式項目_),以及數量不定的引數。回傳值會用對應的引數解析每個_格式項目_,再把解析後的值和固定文字組合起來。
string.Format("I had {0} bitcoins on {1}, the day I forgot my password.", 55.5, new DateTime(2010, 2, 25))
// => "I had 55.5 bitcoins on 2/25/2010 00:00:00, the day I forgot my password." - US settings
在格式被當成某種範本、用在多個運算式裡,或是把格式和要格式化的運算式湊在一起顯得太笨重(例如牽涉到[逐字字串][verbatim-strings])時,Format() 可能是比內插更好的選擇。
這種機制在技術上稱為複合格式。
更完整的字串產生方法清單(會用到_複合格式_的那些)請見這篇文章。
內插字串會以 $ 開頭,並包含放在大括號裡的執行階段運算式。格式項目的語法如下:$"{<interpolationExpression>[,<alignment>][:<formatString>]}"。這樣就不需要另外準備一份引數清單。結果在功能上等同於 String.Format() 機制。
運算式可以包含任何在作用域內的東西。_對齊_是文字所在「欄位」的長度。如果_對齊_是正數,文字左邊會補上空格;如果是負數,空格則會補在文字右邊。
var loadsOf = 55.5;
var thatDay = new DateTime(2010, 2, 25);
$"I had {loadsOf} bitcoins on {thatDay}, the day I forgot my password."
// => "I had 55.5 bitcoins on 2/25/2010 00:00:00, the day I forgot my password." - US settings
放在大括號裡的文字,在複合格式中是預留位置,在字串內插中則是內插運算式,統稱為_格式項目_。
一個格式項目最多可以包含 3 個部分。第一個是必要的運算式或引數預留位置,如上面的範例程式碼所示。此外還有選用的對齊(以逗號「,」帶出)和選用的_格式字串_(以冒號「:」帶出)。
{<interpolationExpression>[,<alignment>][:<formatString>]}
_對齊_指定文字所放置「欄位」的長度,對齊為正數時在左邊補空格,為負數時則在右邊補空格。
_格式字串_指定輸出文字的樣貌,例如數字是否要加上千分位分隔符,或 DateTime 物件是否只輸出日期部分。
以下程式碼示範如何顯示 DateTime 物件的日期部分,以及以指數形式顯示一個浮點數。
var loadsOf = 55.5;
var thatDay = new DateTime(2010, 2, 25);
$"I had {loadsOf:E} bitcoins on {thatDay:d}, the day I forgot my password."
// => I had 5.550000E+001 bitcoins on 02/25/2010, the day I forgot my password. - US settings
string.Format(
"I had {0:E} bitcoins on {1:d}, the day I forgot my password.",
loadsOf, thatDay)
// => I had 5.550000E+001 bitcoins on 02/25/2010, the day I forgot my password. - US settings
數字和日期都有標準格式和自訂格式。_自訂_和_標準_之間並沒有關鍵性的差別,差別只在你是否有機會用格式字元自行組合出格式字串。這裡的「自訂」和 ICustomFormatter 介面無關,那個介面是在開發你自己的自訂格式器時使用的。
基底類別庫(BCL)提供 2 種格式器:DateTimeFormatInfo 和 NumberFormatInfo,以及 6 組格式字串。
各種_格式字串_的清單如下:
Enum 和 GUID 的_格式字串_可以歸類為_標準_。雖然 enum 和 GUID 有接受 IFormatProvider 的 ToString() 多載,但它實際上是否有作用並不清楚。
程式庫中試圖為_格式字串_注入一些一致性(除了它們都以字串表示之外)。這種追求一致性表現在標準字串上。實際上,身為開發者,你很少在意標準字串和自訂字串的差別。雖然如果你為自己的類別實作格式器,最好能沿用現有的標準字串(如果你的類別看起來有這個需要),但大體上你可以忽略這個差別。
每個執行緒都有預設的文化 Thread.CurrentThread.CurrentCulture,封裝在 CultureInfo 的執行個體中。執行緒的文化會決定日期和數字預設如何格式化,並反映地區差異,例如英國慣用的日期格式 DD/MM/YYYY 和美國的 MM/DD/YYYY 就不同。
CultureInfo 實作了 IFormatProvider 介面,可以傳給 String.Format() 的某些多載。這可以用來覆寫執行緒的文化。