字串和字串常值

字串是 String 類型的物件,其值為文字。 就內部而言,文字會儲存為 Char 物件的循序唯讀集合。 C# 字串的結尾沒有終止的 Null 字元,因此 C# 字串可以包含任何數目的內嵌 Null 字元 ('\0')。 字串的 Length 屬性代表它包含的 Char 物件數目,而非 Unicode 字元的數目。 若要存取字串中的個別 Unicode 字碼指標,請使用 StringInfo 物件。

字串與 System.String

在 C# 中,string 關鍵字是 String 的別名,因此,Stringstring 是相等的。 建議使用提供的別名 string,因為即使沒有 using System;,其仍有效。 String 類別提供許多方法來安全地建立、操作和比較字串。 此外,C# 語言會多載一些運算子,以簡化常見的字串作業。 如需關鍵字的詳細資訊,請參閱字串。 如需類型和其方法的詳細資訊,請參閱 String

宣告並初始化字串

您可以透過多種方式宣告並初始化字串,如下列範例所示:

// Declare without initializing.
string message1;

// Initialize to null.
string? message2 = null;

// Initialize as an empty string.
// Use the Empty constant instead of the literal "".
string message3 = System.String.Empty;

// Initialize with a regular string literal.
string oldPath = "c:\\Program Files\\Microsoft Visual Studio 8.0";

// Initialize with a verbatim string literal.
string newPath = @"c:\Program Files\Microsoft Visual Studio 9.0";

// Use System.String if you prefer.
System.String greeting = "Hello World!";

// In local variables (i.e. within a method body)
// you can use implicit typing.
var temp = "I'm still a strongly-typed System.String!";

// Use a const string to prevent 'message4' from
// being used to store another string value.
const string message4 = "You can't get rid of me!";

// Use the String constructor only when creating
// a string from a char*, char[], or sbyte*. See
// System.String documentation for details.
char[] letters = { 'A', 'B', 'C' };
string alphabet = new string(letters);

除了使用字元陣列初始化字串以外,您不能使用 new 運算子建立字串物件。

使用 Empty 常數值初始化字串,以建立字串長度為零的新 String 物件。 零長度字串的字串常值表示法是 ""。 使用 Empty 值初始化字串,而非 null,即可降低發生 NullReferenceException 的機會。 使用靜態 IsNullOrEmpty(String) 方法,先驗證字串的值,再嘗試進行存取。

字串的不變性

字串物件為「不可變」:其在建立之後將無法變更。 所有看似會修改字串的 String 方法和 C# 運算子,實際上會以新的字串物件傳回結果。 在下列範例中,當 s1s2 的內容串連以組成單一字串時,兩個原始字串將不會被修改。 += 運算子會建立新的字串,其中包含結合的內容。 新的物件會指派給變數 s1,而先前指派給 s1 的原始物件將會被釋放以進行記憶體回收,因為已經沒有其他具有其參考的變數。

string s1 = "A string is more ";
string s2 = "than the sum of its chars.";

// Concatenate s1 and s2. This actually creates a new
// string object and stores it in s1, releasing the
// reference to the original object.
s1 += s2;

System.Console.WriteLine(s1);
// Output: A string is more than the sum of its chars.

因為對字串的「修改」實際上是建立新的字串,當您建立對字串的參考時,必須特別謹慎。 如果您建立對字串的參考,然後「修改」原始字串,該參考將會繼續指向原始物件,而非修改字串時所建立的新物件。 下列程式碼說明這個行為:

string str1 = "Hello ";
string str2 = str1;
str1 += "World";

System.Console.WriteLine(str2);
//Output: Hello

如需如何建立以修改為基礎 (例如原始字串上的搜尋與取代作業) 之新字串的詳細資訊,請參閱如何:修改字串內容

引用的字串常值

引用的字串常值會在同一行以單引號字元 (") 開頭和結尾。 引用字串常值最適合用於符合單行且不包含任何逸出序列的字串。 如下列範例所示,引用字串常值必須內嵌逸出字元:

string columns = "Column 1\tColumn 2\tColumn 3";
//Output: Column 1        Column 2        Column 3

string rows = "Row 1\r\nRow 2\r\nRow 3";
/* Output:
    Row 1
    Row 2
    Row 3
*/

string title = "\"The \u00C6olean Harp\", by Samuel Taylor Coleridge";
//Output: "The Æolean Harp", by Samuel Taylor Coleridge

逐字字串常值

逐字字串常值對於多行字串、包含反斜線字元或內嵌雙引號的字串更為方便。 逐字字串會保留新行字元做為字串文字的一部分。 使用雙引號在逐字字串中內嵌引號。 下列範例示範一些逐字字串的常見用法︰

string title = "\"The \u00C6olean Harp\", by Samuel Taylor Coleridge";
//Output: "The Æolean Harp", by Samuel Taylor Coleridge

string filePath = @"C:\Users\scoleridge\Documents\";
//Output: C:\Users\scoleridge\Documents\

string text = @"My pensive SARA ! thy soft cheek reclined
    Thus on mine arm, most soothing sweet it is
    To sit beside our Cot,...";
/* Output:
My pensive SARA ! thy soft cheek reclined
    Thus on mine arm, most soothing sweet it is
    To sit beside our Cot,...
*/

string quote = @"Her name was ""Sara.""";
//Output: Her name was "Sara."

原始字串常值

從 C# 11 開始,您可以使用原始字串常值,更輕鬆地建立多行字串,或使用任何需要逸出序列的字元。 原始字串常值不需要使用逸出序列。 您可以撰寫字串,包括空白字元格式化,表示您希望其在輸出中顯示的方式。 原始字串常值

  • 以至少三個雙引號字元 (""") 的序列開頭和結尾。 您可以透過三個以上的連續字元開始和結束序列,以支援包含三個 (或更多) 重複引用字元的字串常值。
  • 單行原始字串常值需要同一行的開頭和結尾引用字元。
  • 多行原始字串常值同時需要各自行的開頭和結尾引用字元。
  • 在多行原始字串常值中,結尾引號左邊的任何空白字元都會從原始字串常值的所有行中移除。
  • 在多行原始字串常值中,忽略相同行上開頭引號後面的空白字元。
  • 在多行原始字串常值中,字串常值中只包含開頭引號後面的空白字元。

下列範例將示範這些規則:

string singleLine = """Friends say "hello" as they pass by.""";
string multiLine = """
    "Hello World!" is typically the first program someone writes.
    """;
string embeddedXML = """
       <element attr = "content">
           <body style="normal">
               Here is the main text
           </body>
           <footer>
               Excerpts from "An amazing story"
           </footer>
       </element >
       """;
// The line "<element attr = "content">" starts in the first column.
// All whitespace left of that column is removed from the string.

string rawStringLiteralDelimiter = """"
    Raw string literals are delimited 
    by a string of at least three double quotes,
    like this: """
    """";

下列範例示範根據這些規則所報告的編譯器錯誤:

// CS8997: Unterminated raw string literal.
var multiLineStart = """This
    is the beginning of a string 
    """;

// CS9000: Raw string literal delimiter must be on its own line.
var multiLineEnd = """
    This is the beginning of a string """;

// CS8999: Line does not start with the same whitespace as the closing line
// of the raw string literal
var noOutdenting = """
    A line of text.
Trying to outdent the second line.
    """;

前兩個範例無效,因為多行原始字串常值需要各自行的開頭和結尾引號序列。 第三個範例無效,因為文字會從結束引號序列中凸排。

當您產生的文字中包含使用引號字串常值或逐字字串常值時需要逸出序列的字元時,您應該考慮原始字串常值。 原始字串常值對您和其他人來說更容易閱讀,因為其會更類似輸出文字。 例如,請考慮下列包含格式化 JSON 字串的程式碼:

string jsonString = """
{
  "Date": "2019-08-01T00:00:00-07:00",
  "TemperatureCelsius": 25,
  "Summary": "Hot",
  "DatesAvailable": [
    "2019-08-01T00:00:00-07:00",
    "2019-08-02T00:00:00-07:00"
  ],
  "TemperatureRanges": {
    "Cold": {
      "High": 20,
      "Low": -10
    },
    "Hot": {
      "High": 60,
      "Low": 20
    }
            },
  "SummaryWords": [
    "Cool",
    "Windy",
    "Humid"
  ]
}
""";

比較該文字與 JSON 還原序列化範例中的對等文字,後者不會利用這項新功能。

字串逸出序列

逸出序列 字元名稱 Unicode 編碼
\' 單引號 0x0027
\" 雙引號 0x0022
\\ 反斜線 0x005C
\0 Null 0x0000
\a 警示 0x0007
\b 退格鍵 0x0008
\f 換頁字元 0x000C
\n 新行 0x000A
\r 歸位字元 0x000D
\t 水平 Tab 鍵 0x0009
\v 垂直 Tab 鍵 0x000B
\u Unicode 逸出序列 (UTF-16) \uHHHH (範圍:0000 - FFFF;範例:\u00E7 = "ç")
\U Unicode 逸出序列 (UTF-32) \U00HHHHHH (範圍:000000 - 10FFFF;範例:\U0001F47D = "👽")
\x 類似 "\u" (除了變數長度之外) 的 Unicode 逸出序列 \xH[H][H][H] (範圍:0 - FFFF;範例:\x00E7\x0E7\xE7 = "ç")

警告

當使用 \x 逸出序列且指定的十六進位數字少於 4 個時,若尾隨在逸出序列之後的字元是有效的十六進位數字 (亦即 0-9、A-F 及 a-f),這些數字將會被解譯為逸出序列的一部分。 例如,\xA1 會產生 "¡",亦即字碼指標 U+00A1。 倘若下一個字元為 "A" 或 "a",則逸出序列將會被解譯為 \xA1A,進而產生 "ਚ",亦即字碼指標 U+0A1A。 由此可知,將 4 個數字全數指定為十六進位數字 (例如 \x00A1),可避免可能的錯譯。

注意

在編譯時期,逐字字串和原始字串會轉換為具有所有相同逸出序列的一般字串。 因此,如果您在偵錯工具監看式視窗中檢視逐字或原始字串,您會看到由編譯器新增的逸出字元,而非來自於您原始程式碼的逐字或原始版本。 例如,逐字字串 @"C:\files.txt" 會在監看式視窗中顯示為 "C:\\files.txt"。

格式化字串

格式化字串是可在執行階段動態決定其內容的字串。 格式字串是透過內嵌「插入的運算式」或字串內大括弧內的預留位置來建立的。 大括弧 ({...}) 內的所有內容都會被解析為一個值,並在執行階段以格式化字串形式輸出。 有兩種方式可用來建立格式字串:字串插補與複合格式設定。

字串插補

插補字串可透過 $ 特殊字元識別,而且在大括弧中包括插補運算式。 如果您是字串插補的新手,請參閱字串插補 - C# 互動式教學課程以取得快速概觀。

使用字串插補來改進您程式碼的可讀性與可維護性。 字串插補可達成與 String.Format 方法相同的結果,但可改進使用方便性與內嵌簡潔度。

var jh = (firstName: "Jupiter", lastName: "Hammon", born: 1711, published: 1761);
Console.WriteLine($"{jh.firstName} {jh.lastName} was an African American poet born in {jh.born}.");
Console.WriteLine($"He was first published in {jh.published} at the age of {jh.published - jh.born}.");
Console.WriteLine($"He'd be over {Math.Round((2018d - jh.born) / 100d) * 100d} years old today.");

// Output:
// Jupiter Hammon was an African American poet born in 1711.
// He was first published in 1761 at the age of 50.
// He'd be over 300 years old today.

從 C# 10 開始,當用於預留位置的所有運算式也是常數字串時,您可以使用字串插補來初始化常數字串。

從 C# 11 開始,您可以將原始字串常值插補結合。 您以三個或多個連續雙引號來開始和結束格式化字串。 如果輸出字串應該包含 {} 字元,您可以使用額外的 $ 字元,指定開始和結束插補的 {} 字元數目。 輸出中包含任何較少 {} 字元的任何序列。 下列範例示範如何使用該功能來顯示點與原點之間的距離,並將該點放在大括弧內:

int X = 2;
int Y = 3;

var pointMessage = $$"""The point {{{X}}, {{Y}}} is {{Math.Sqrt(X * X + Y * Y)}} from the origin.""";

Console.WriteLine(pointMessage);
// Output:
// The point {2, 3} is 3.605551275463989 from the origin.

逐字字串插補

C# 也允許逐字字串插補,例如,跨多行使用 $@@$ 語法。

若要逐字解譯逸出序列,請使用逐字字串常值。 插補逐字字串以 $ 字元為開頭,後面接著 @ 字元。 您可以依任何順序使用 $@ 權杖:$@"..."@$"..." 都是有效的插補逐字字串。

var jh = (firstName: "Jupiter", lastName: "Hammon", born: 1711, published: 1761);
Console.WriteLine($@"{jh.firstName} {jh.lastName}
    was an African American poet born in {jh.born}.");
Console.WriteLine(@$"He was first published in {jh.published}
at the age of {jh.published - jh.born}.");

// Output:
// Jupiter Hammon
//     was an African American poet born in 1711.
// He was first published in 1761
// at the age of 50.

複合格式

String.Format 利用大括弧內的預留位置來建立格式字串。 此範例可產生與上面使用之字串插補方法類似的輸出。

var pw = (firstName: "Phillis", lastName: "Wheatley", born: 1753, published: 1773);
Console.WriteLine("{0} {1} was an African American poet born in {2}.", pw.firstName, pw.lastName, pw.born);
Console.WriteLine("She was first published in {0} at the age of {1}.", pw.published, pw.published - pw.born);
Console.WriteLine("She'd be over {0} years old today.", Math.Round((2018d - pw.born) / 100d) * 100d);

// Output:
// Phillis Wheatley was an African American poet born in 1753.
// She was first published in 1773 at the age of 20.
// She'd be over 300 years old today.

如需設定 .NET 類型格式的詳細資訊,請參閱 .NET 中的格式設定類型

子字串

子字串是包含在字串中的任何字元序列。 使用 Substring 方法,來從原始字串的一部分建立新的字串。 您可以使用 IndexOf 方法,來搜尋子字串的一或多個出現位置。 使用 Replace 方法,以新字串取代所有指定的子字串。 與 Substring 方法類似,Replace 實際上會傳回新字串,並不會修改原始字串。 如需詳細資訊,請參閱如何搜尋字串以及如何修改字串內容

string s3 = "Visual C# Express";
System.Console.WriteLine(s3.Substring(7, 2));
// Output: "C#"

System.Console.WriteLine(s3.Replace("C#", "Basic"));
// Output: "Visual Basic Express"

// Index values are zero-based
int index = s3.IndexOf("C");
// index = 7

存取個別字元

您可以搭配索引值使用陣列標記法來取得個別字元的唯讀存取權,如下列範例所示:

string s5 = "Printing backwards";

for (int i = 0; i < s5.Length; i++)
{
    System.Console.Write(s5[s5.Length - i - 1]);
}
// Output: "sdrawkcab gnitnirP"

如果 String 方法不提供修改字串中個別字元的必要功能,您可以使用 StringBuilder 物件「就地」修改個別字元,然後使用 StringBuilder 方法建立新的字串來儲存結果。 在下列範例中,假設您必須以特定方式修改原始字串,並儲存結果以供日後使用︰

string question = "hOW DOES mICROSOFT wORD DEAL WITH THE cAPS lOCK KEY?";
System.Text.StringBuilder sb = new System.Text.StringBuilder(question);

for (int j = 0; j < sb.Length; j++)
{
    if (System.Char.IsLower(sb[j]) == true)
        sb[j] = System.Char.ToUpper(sb[j]);
    else if (System.Char.IsUpper(sb[j]) == true)
        sb[j] = System.Char.ToLower(sb[j]);
}
// Store the new string.
string corrected = sb.ToString();
System.Console.WriteLine(corrected);
// Output: How does Microsoft Word deal with the Caps Lock key?

Null 字串和空字串

空字串是 System.String 物件的執行個體,其中包含零個字元。 空字串經常用於各種程式設計案例中,來表示空白的文字欄位。 您可以對空字串呼叫方法,因為其是有效的 System.String 物件。 空字串會以下列方式初始化︰

string s = String.Empty;

相較之下,Null 字串指的不是 System.String 物件執行個體,而且對 Null 字串呼叫方法的任何嘗試都會導致 NullReferenceException。 不過,您可以在搭配其他字串的串連和比較作業中使用 Null 字串。 下列範例說明對 Null 字串的參考會造成 (以及不會造成) 擲回例外狀況的一些情況︰

string str = "hello";
string? nullStr = null;
string emptyStr = String.Empty;

string tempStr = str + nullStr;
// Output of the following line: hello
Console.WriteLine(tempStr);

bool b = (emptyStr == nullStr);
// Output of the following line: False
Console.WriteLine(b);

// The following line creates a new empty string.
string newStr = emptyStr + nullStr;

// Null strings and empty strings behave differently. The following
// two lines display 0.
Console.WriteLine(emptyStr.Length);
Console.WriteLine(newStr.Length);
// The following line raises a NullReferenceException.
//Console.WriteLine(nullStr.Length);

// The null character can be displayed and counted, like other chars.
string s1 = "\x0" + "abc";
string s2 = "abc" + "\x0";
// Output of the following line: * abc*
Console.WriteLine("*" + s1 + "*");
// Output of the following line: *abc *
Console.WriteLine("*" + s2 + "*");
// Output of the following line: 4
Console.WriteLine(s2.Length);

使用 StringBuilder 進行快速字串建立

.NET 中的字串作業已高度最佳化,在大部分的情況下不會大幅影響效能。 不過,在部分案例中 (例如執行數百或數千次的緊密迴圈),字串作業可能會影響效能。 StringBuilder 類別會建立一個字串緩衝區,能在您的程式執行許多字串操作的情況下提供較佳的效能。 StringBuilder 字串也可讓您重新指派內建字串資料類型所不支援的個別字元。 例如,下列程式碼能在不建立新字串的情況下變更字串內容:

System.Text.StringBuilder sb = new System.Text.StringBuilder("Rat: the ideal pet");
sb[0] = 'C';
System.Console.WriteLine(sb.ToString());
//Outputs Cat: the ideal pet

在下列範例中,將使用 StringBuilder 物件從一組數字類型建立字串:

var sb = new StringBuilder();

// Create a string composed of numbers 0 - 9
for (int i = 0; i < 10; i++)
{
    sb.Append(i.ToString());
}
Console.WriteLine(sb);  // displays 0123456789

// Copy one character of the string (not possible with a System.String)
sb[0] = sb[9];

Console.WriteLine(sb);  // displays 9123456789

字串、擴充方法和 LINQ

因為 String 類型會實作 IEnumerable<T>,所以您可以對字串使用 Enumerable 類別中所定義的擴充方法。 為了避免視覺雜亂,這些方法會從 String 型別的 IntelliSense 中排除,不過您還是可以使用加以。 您也可以在字串上使用 LINQ 查詢運算式。 如需詳細資訊,請參閱 LINQ 和字串