字串插值
字串插值允許開發人員將 variables
和 text 組合在一起形成一個字串。
基本例子
建立了兩個 int
變數:foo
和 bar
。
int foo = 34;
int bar = 42;
string resultString = $"The foo is {foo}, and the bar is {bar}.";
Console.WriteLine(resultString);
輸出 :
foo 是 34,bar 是 42。
仍然可以使用字串中的大括號,如下所示:
var foo = 34;
var bar = 42;
// String interpolation notation (new style)
Console.WriteLine($"The foo is {{foo}}, and the bar is {{bar}}.");
這會產生以下輸出:
foo 是{foo},條形是{bar}。
使用插值與逐字字串文字
在字串之前使用 @
會導致字串被逐字解釋。因此,例如 Unicode 字元或換行符將保持與鍵入的完全一致。但是,這不會影響插值字串中的表示式,如以下示例所示:
Console.WriteLine($@"In case it wasn't clear:
\u00B9
The foo
is {foo},
and the bar
is {bar}.");
輸出:
如果不清楚:
\ u00B9
foo
是 34,
條形
是 42。
表示式
使用字串插值,也可以計算花括號 {}
中的表示式。結果將插入字串中的相應位置。例如,要計算 foo
和 bar
的最大值並插入它,請在花括號內使用 Math.Max
:
Console.WriteLine($"And the greater one is: { Math.Max(foo, bar) }");
輸出:
更大的是:42
注意:大括號和表示式之間的任何前導或尾隨空格(包括空格,製表符和 CRLF /換行符)都將被完全忽略,並且不包含在輸出中
另一個例子,變數可以格式化為貨幣:
Console.WriteLine($"Foo formatted as a currency to 4 decimal places: {foo:c4}");
輸出:
Foo 格式化為 4 位小數的貨幣:$ 34.0000
或者它們可以格式化為日期:
Console.WriteLine($"Today is: {DateTime.Today:dddd, MMMM dd - yyyy}");
輸出:
今天是:2015 年 7 月 20 日星期一
也可以在插值中評估帶條件(三元)運算子的語句。但是,這些必須用括號括起來,因為冒號用於表示格式,如上所示:
Console.WriteLine($"{(foo > bar ? "Foo is larger than bar!" : "Bar is larger than foo!")}");
輸出:
bar 比 foo 大!
條件表示式和格式說明符可以混合使用:
Console.WriteLine($"Environment: {(Environment.Is64BitProcess ? 64 : 32):00'-bit'} process");
輸出:
環境:32 位程序
轉義序列
對於逐字和非逐字字串文字,轉義反斜槓(\
)和引用("
)字元在插值字串中的工作方式與非插值字串完全相同:
Console.WriteLine($"Foo is: {foo}. In a non-verbatim string, we need to escape \" and \\ with backslashes.");
Console.WriteLine($@"Foo is: {foo}. In a verbatim string, we need to escape "" with an extra quote, but we don't need to escape \");
輸出:
Foo 是 34.在一個非逐字的字串中,我們需要轉義“和\用反斜槓
.Foo 是 34.在一個逐字字串中,我們需要通過額外的引用來轉義,但我們不需要轉義\
要在插值字串中包含大括號 {
或 }
,請使用兩個花括號 {{
或 }}
:
$"{{foo}} is: {foo}"
輸出:
{foo}是:34
FormattableString 型別
$"..."
字串插值表示式的型別並不總是簡單的字串。編譯器根據上下文決定分配哪種型別:
string s = $"hello, {name}";
System.FormattableString s = $"Hello, {name}";
System.IFormattable s = $"Hello, {name}";
當編譯器需要選擇要呼叫哪個過載方法時,這也是型別首選項的順序。
一個新型 ,System.FormattableString
,表示一個複合格式字串,隨著要被格式化的引數。使用它來編寫專門處理插值引數的應用程式:
public void AddLogItem(FormattableString formattableString)
{
foreach (var arg in formattableString.GetArguments())
{
// do something to interpolation argument 'arg'
}
// use the standard interpolation and the current culture info
// to get an ordinary String:
var formatted = formattableString.ToString();
// ...
}
呼叫上面的方法:
AddLogItem($"The foo is {foo}, and the bar is {bar}.");
例如,如果日誌記錄級別已經過濾掉日誌項,則可以選擇不產生格式化字串的效能成本。
隱含的轉換
內插字串有隱式型別轉換:
var s = $"Foo: {foo}";
System.IFormattable s = $"Foo: {foo}";
你還可以生成一個 IFormattable
變數,允許你使用不變上下文轉換字串:
var s = $"Bar: {bar}";
System.FormattableString s = $"Bar: {bar}";
現在和不變的文化方法
如果開啟程式碼分析,插值字串將全部產生警告 CA1305 (指定 IFormatProvider
)。靜態方法可用於應用當前文化。
public static class Culture
{
public static string Current(FormattableString formattableString)
{
return formattableString?.ToString(CultureInfo.CurrentCulture);
}
public static string Invariant(FormattableString formattableString)
{
return formattableString?.ToString(CultureInfo.InvariantCulture);
}
}
然後,要為當前文化生成正確的字串,只需使用以下表示式:
Culture.Current($"interpolated {typeof(string).Name} string.")
Culture.Invariant($"interpolated {typeof(string).Name} string.")
注意 :Current
和 Invariant
不能建立為擴充套件方法,因為預設情況下,編譯器將型別 String
分配給插值字串表示式,導致以下程式碼無法編譯:
$"interpolated {typeof(string).Name} string.".Current();
FormattableString
類已經包含了 Invariant()
方法,所以轉換到不變文化的最簡單方法是依靠 using static
:
using static System.FormattableString;
string invariant = Invariant($"Now = {DateTime.Now}");
string current = $"Now = {DateTime.Now}";
在幕後
插值字串只是 String.Format()
的語法糖。編譯器( Roslyn )將在幕後將其變為 String.Format
:
var text = $"Hello {name + lastName}";
以上內容將轉換為以下內容:
string text = string.Format("Hello {0}", new object[] {
name + lastName
});
字串插值和 Linq
可以在 Linq 語句中使用插值字串來進一步提高可讀性。
var fooBar = (from DataRow x in fooBarTable.Rows
select string.Format("{0}{1}", x["foo"], x["bar"])).ToList();
可以重寫為:
var fooBar = (from DataRow x in fooBarTable.Rows
select $"{x["foo"]}{x["bar"]}").ToList();
可重複使用的插值字串
使用 string.Format
,你可以建立可重用的格式字串:
public const string ErrorFormat = "Exception caught:\r\n{0}";
// ...
Logger.Log(string.Format(ErrorFormat, ex));
但是,插值字串不會使用佔位符引用不存在的變數進行編譯。以下內容無法編譯:
public const string ErrorFormat = $"Exception caught:\r\n{error}";
// CS0103: The name 'error' does not exist in the current context
相反,建立一個消耗變數並返回 String
的 Func<>
:
public static Func<Exception, string> FormatError =
error => $"Exception caught:\r\n{error}";
// ...
Logger.Log(FormatError(ex));
字串插值和本地化
如果你正在本地化你的應用程式,你可能想知道是否可以使用字串插值和本地化。確實,有可能儲存資原始檔 String
s,如:
"My name is {name} {middlename} {surname}"
而不是可讀性低得多:
"My name is {0} {1} {2}"
String
插值過程在編譯時發生,與在執行時發生的帶有 string.Format
的格式化字串不同。插值字串中的表示式必須引用當前上下文中的名稱,並且需要儲存在資原始檔中。這意味著如果你想使用本地化,你必須這樣做:
var FirstName = "John";
// method using different resource file "strings"
// for French ("strings.fr.resx"), German ("strings.de.resx"),
// and English ("strings.en.resx")
void ShowMyNameLocalized(string name, string middlename = "", string surname = "")
{
// get localized string
var localizedMyNameIs = Properties.strings.Hello;
// insert spaces where necessary
name = (string.IsNullOrWhiteSpace(name) ? "" : name + " ");
middlename = (string.IsNullOrWhiteSpace(middlename) ? "" : middlename + " ");
surname = (string.IsNullOrWhiteSpace(surname) ? "" : surname + " ");
// display it
Console.WriteLine($"{localizedMyNameIs} {name}{middlename}{surname}".Trim());
}
// switch to French and greet John
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("fr-FR");
ShowMyNameLocalized(FirstName);
// switch to German and greet John
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("de-DE");
ShowMyNameLocalized(FirstName);
// switch to US English and greet John
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("en-US");
ShowMyNameLocalized(FirstName);
如果上面使用的語言的資源字串正確儲存在各個資原始檔中,則應獲得以下輸出:
Bonjour,mon nom est John
Hallo,mein Name ist John
你好,我叫約翰
請注意,這意味著名稱遵循每種語言的本地化字串。如果不是這種情況,則需要將佔位符新增到資源字串並修改上面的函式,或者需要查詢函式中的文化資訊並提供包含不同情況的 switch case 語句。有關資原始檔的更多詳細資訊,請參閱如何在 C#中使用本地化 。
如果翻譯不可用,最好使用大多數人都能理解的預設後備語言。我建議使用英語作為預設的後備語言。
遞迴插值
雖然不是很有用,但允許在另一個大括號內遞迴使用插值 string
:
Console.WriteLine($"String has {$"My class is called {nameof(MyClass)}.".Length} chars:");
Console.WriteLine($"My class is called {nameof(MyClass)}.");
輸出:
字串有 27 個字元:
我的類叫做 MyClass。