原始字串常值

Tip

本文屬於 基礎部分, 適合已經至少懂一種程式語言並正在學習 C# 的開發者。 如果你是程式新手,建議先從 入門 教學開始。 完整文法請參見語言參考。

來自另一種語言? C# 的原始字串字面值與 Python 和 Rust 的 r"..." 字串、Java 的文字區塊("""...""")以及 JavaScript、TypeScript 和 Go 中的反向擷取範本字串扮演相同角色。 C# 語法最接近 Java 的文字區塊,並加入了可變長度分隔符和插值的額外規則。

一個原始字串的字面值會被三個或以上的雙引號所界定。 在分隔符內,每個字元皆視為字面值。 引號和反斜線不需要逃逸,換行符則保持原文。 對於包含引號、反斜線或多行的字串,請使用原始字串:JSON、XML、SQL、正則表達式、檔案路徑及程式碼範例。

Warning

原始字串的字面值讓 SQL 更容易閱讀,但並不代表 SQL 更安全。 切勿將使用者提供的數值串接或插值入 SQL 指令。 這種做法會讓你的應用程式更容易被 SQL 注入。 改用參數化指令:DbCommand.CreateParameter 搭配 DbParameterCollection.Add,或使用 Entity Framework CoreDapper 中較高階的輔助程式。 同樣的注意事項也適用於其他容易注入的格式,如 shell 指令、LDAP 濾波器和 HTML。

包含引號和反斜線的字面值

一般常值需要對 "\ 進行逸出。 逐字字面文字仍需 "" 嵌入引文。 原始的直譯兩者都不需要:

// Same JSON value, three ways:
string regular  = "{ \"name\": \"Ada\", \"path\": \"C:\\\\src\" }";
string verbatim = @"{ ""name"": ""Ada"", ""path"": ""C:\\src"" }";
string raw      = """{ "name": "Ada", "path": "C:\\src" }""";

Console.WriteLine(regular  == raw);   // True
Console.WriteLine(verbatim == raw);   // True

每個表單產生的字串都一樣,但原始版本的讀取方式和它所代表的 JSON 完全一樣。

單行原始字串

開頭與結尾分隔符各至少包含三個雙引號,結尾分隔符必須使用與開頭分隔符相同數量的引號。 內容位於兩者之間,且在同一行上。 內容中的引號和反斜線是字面上的:

// A raw string literal starts and ends with at least three quotes.
// Inside, " and \ are literal — no escaping required.
string message = """She said "hi" and left.""";
string regex   = """\d{3}-\d{4}""";

Console.WriteLine(message);   // She said "hi" and left.
Console.WriteLine(regex);     // \d{3}-\d{4}

單行原始字串在其分隔符之間不可能空。 它可以以雙引號結尾,但不能以雙引號開頭。 編譯器會將前導雙引號視為額外的開頭分隔字元。 如果內容必須以引號開頭,請改用多行原始字串常值,讓內容位於獨立的一行,如此一來,開頭的引號就不會產生歧義。

多行原始字串

對於多行內容,起始分隔符位於該行末尾,而結束分隔符則位於另一行的開頭。 與單行原始字串相同,分隔符包含三個或以上的雙引號,且結尾分隔符必須使用與開頭相同的引號數量。 三段引號是常見情況,但當內容本身包含 """一段 時,也可以使用四、五條甚至更多。 兩個分隔符之間的所有部分都是字串的值,完全如下寫法:

// The opening """ and closing """ each sit on their own line.
// The content between them is the value, exactly as written.
string sql = """
    SELECT id, name
    FROM customers
    WHERE active = 1
    """;

Console.WriteLine(sql);

緊接在開頭 """ 之後的換行,以及緊接在結尾 """ 之前的換行,都不是值的一部分。 它們是分隔用的空白字元。 同樣地,編譯器會從每行內容行中移除閉尾 """ 左邊的空白,這樣你可以縮排字面值以匹配其包圍的程式碼區塊,而不會在字串中出現縮排。 下一節將詳細介紹此規則。

如果內容本身包含一串 """,請使用四個或更多引號作為分隔符號。 分隔符的數量只要超過內容中最長的引用連段即可。 完整規則請參閱 原始字串常值(語言參考)

縮排:結束分隔符號決定邊界

結束 """ 所在的欄位定義了左邊距。 編譯器會從每個內容行中移除直到該欄為止的空白字元。 此規則允許你縮排字面值以匹配周圍程式碼,而不破壞該值:

// The column of the closing """ sets a left margin.
// Whitespace up to that column is stripped from every content line.
string xml = """
        <order id="42">
            <item>book</item>
        </order>
        """;

// First content line begins at column 0 of the value:
Console.WriteLine(xml);
/* Output:
   <order id="42">
       <item>book</item>
   </order>
 */

如果某一行的前置空白字元少於結尾分隔符欄位的字元,編譯器會回報錯誤。 所有內容行至少要縮排到結尾 """的程度。

原始插值字串

在原始字串前加上 $ 前綴,以啟用插值功能。 評估孔洞中的 {} 表達式,並將其結果代入以下數值:

// A single $ before """ enables interpolation: single { and } mark a hole.
// Inside a single-$ raw string, literal braces aren't allowed — use $$ when
// the content also contains literal { or }.
string name = "Ada";
int    score = 95;

string report = $"""
    Player:  {name}
    Score:   {score}
    Updated: {DateTime.UtcNow:yyyy-MM-dd}
    """;

Console.WriteLine(report);

如果你的插值內容也需要字面上的 {} 字元,請參閱 原始字串常值(語言參考)

何時選擇哪個字面

只要內容包含引號、反斜線或多行,就使用 原始字串字面值 。 如此一來,內容讀起來更精簡,貼入或貼出都更方便,而且不會有跳脫序列錯誤。

對於短且單行的值(如名稱、訊息、格式佔位符),使用 規則字串的字面值 ,且不加引號或反斜線。

只有在處理已有使用這些字串的程式碼時,才使用 逐字字串字面值@"...")。 對於新程式碼,原始字串可涵蓋逐字字串的所有使用情況,而且在內嵌引號時語法更簡潔。

參見