Menu

Chuỗi trong C#: Length, so sánh, ký tự escape, chuỗi verbatim và nhiều dòng

Cách chuỗi hoạt động trong C#: tính bất biến, Length và chỉ số, so sánh bằng ==, Equals và string.Compare, ký tự escape, chuỗi verbatim @ và văn bản nhiều dòng, raw string literal, kiểm tra null và rỗng, duyệt qua từng ký tự.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Một string trong C# là một dãy ký tự UTF-16 (các giá trị char). Đây là kiểu tham chiếu, nhưng hoạt động giống một giá trị ở những điểm quan trọng: chuỗi là bất biến (immutable), và == so sánh nội dung văn bản chứ không so sánh danh tính đối tượng. string là từ khóa C# cho kiểu .NET System.String; hai cách viết dùng thay cho nhau được.

Đầu ra:

Porto
5
0
*****
hi
Porto has 5 letters

Chuỗi là bất biến

Không có phương thức nào thay đổi chuỗi tại chỗ. ToUpper, Replace, Trim và mọi phương thức khác đều trả về một chuỗi mới và giữ nguyên chuỗi gốc. Quên dùng giá trị trả về là một bug rất hay gặp với chuỗi:

Đầu ra:

[  maya  ]
[MAYA]
tea
tea and cake

Tính bất biến giúp chuỗi an toàn khi chia sẻ giữa các luồng và khi dùng làm khóa của dictionary. Cái giá phải trả là việc ghép chuỗi từng mảnh trong vòng lặp sẽ tạo ra một chuỗi mới ở mỗi bước; trường hợp đó hãy dùng StringBuilder.

Length và chỉ số

Length là một thuộc tính (không có dấu ngoặc). Indexer s[i] trả về char ở vị trí i, đếm từ 0, và chỉ đọc được:

Đầu ra:

7
A
1
IndexOutOfRangeException
XB-2931

C# 8 trở đi còn chấp nhận chỉ số tính từ cuối: code[^1] là ký tự cuối cùng, giống code[code.Length - 1]. Để lấy nhiều ký tự cùng lúc, xem substring.

Length đếm số đơn vị mã UTF-16, không phải số ký tự mà người đọc nhìn thấy. Phần lớn chữ cái trong phần lớn ngôn ngữ chiếm một đơn vị, nhưng emoji và một số hệ chữ hiếm dùng hai đơn vị (một cặp surrogate), nên "👍".Length bằng 2.

So sánh chuỗi

== và != so sánh từng ký tự, có phân biệt hoa thường. Với các trường hợp khác, hãy truyền một giá trị StringComparison cho biết cách so sánh:

Đầu ra:

False
False
True
True
apple first
True
False
False
True

Nên dùng phép so sánh nào:

  • Ordinal so sánh mã số của các ký tự. Nhanh, dễ đoán và giống nhau trên mọi máy. Dùng cho định danh, tên tệp, khóa, giá trị giao thức và mọi thứ không hiển thị cho người dùng.
  • OrdinalIgnoreCase giống như trên nhưng bỏ qua hoa thường. Đây là lựa chọn đúng cho các phép kiểm tra không phân biệt hoa thường như phần mở rộng tệp hay HTTP header.
  • CurrentCulture (và CurrentCultureIgnoreCase) theo quy tắc ngôn ngữ của người dùng, nên sắp xếp từ ngữ như từ điển của ngôn ngữ đó. Dùng để sắp xếp danh sách hiển thị cho người dùng.
  • InvariantCulture dùng quy tắc theo văn hóa nhưng không phụ thuộc vào cài đặt ngôn ngữ của người dùng. Nó vẫn phụ thuộc vào thư viện toàn cầu hóa: .NET hiện đại dùng ICU, .NET Framework dùng NLS của Windows, và hai thư viện này có thể sắp xếp một số chuỗi khác nhau.

string.Compare(a, b) và a.CompareTo(b) khi không có StringComparison sẽ dùng văn hóa hiện tại, khiến kết quả thay đổi giữa các máy. Tránh so sánh kiểu a.ToLower() == b.ToLower(): cách này cấp phát hai chuỗi mới và cho kết quả sai ở một số văn hóa (chữ i có chấm và không chấm trong tiếng Thổ Nhĩ Kỳ là ví dụ kinh điển).

Gọi a.Equals(b) khi a là null sẽ ném NullReferenceException, còn a == b và phương thức tĩnh string.Equals(a, b) xử lý null an toàn.

Ký tự escape

Bên trong một string literal thông thường, dấu gạch chéo ngược mở đầu một chuỗi escape (escape sequence):

EscapeÝ nghĩa
\nXuống dòng (line feed)
\rVề đầu dòng (carriage return)
\tTab
\\Dấu gạch chéo ngược
\"Dấu nháy kép
\'Dấu nháy đơn (chỉ cần trong literal char)
\0Ký tự null
\uXXXXKý tự UTF-16 theo mã hex 4 chữ số, ví dụ \u00e9
\U0001F44DKý tự theo mã hex 8 chữ số; trên U+FFFF nó thành hai giá trị char
\xH đến \xHHHHKý tự theo 1 đến 4 chữ số hex (nên tránh: độ dài dễ gây nhầm)
\a, \b, \f, \vCảnh báo, backspace, form feed, tab dọc
\eKý tự escape, U+001B (C# 13)

Đầu ra:

Name:	Ana
City:	Porto
She said "hi"
C:\temp\report.txt
cafe = cafe
' is a quote char

Một chuỗi không nhận dạng được như "\d" là lỗi biên dịch (CS1009, unrecognized escape sequence), đây chính là điều xảy ra khi đường dẫn Windows hoặc biểu thức chính quy được viết bằng chuỗi thông thường.

Chuỗi verbatim và chuỗi nhiều dòng

Thêm tiền tố @ trước literal sẽ biến nó thành chuỗi verbatim: dấu gạch chéo ngược là ký tự bình thường, và chuỗi có thể trải trên nhiều dòng. Escape duy nhất còn lại là "", tạo ra một dấu nháy kép.

Đầu ra:

C:\Users\ana\Documents\notes.txt
\d{3}-\d{4}
He said "ready" and left.
Rua das Flores 12
4050-262 Porto
Portugal
first line
second line

Một chuỗi verbatim nhiều dòng chứa mọi thứ nằm giữa hai dấu nháy, kể cả phần thụt lề của các dòng tiếp theo, vì vậy chúng thường được viết sát lề như ví dụ trên. Các dấu xuống dòng bên trong là dấu xuống dòng của tệp nguồn, nên một tệp lưu với kiểu xuống dòng của Windows sẽ cho ra \r\n.

C# 11 bổ sung raw string literal, giải quyết cả hai vấn đề. Chúng bắt đầu và kết thúc bằng ba dấu nháy kép trở lên, hoàn toàn không cần escape, và bỏ đi phần thụt lề bằng đúng mức thụt lề của dấu nháy đóng:

// C# 11 and later
string json = """
    {
      "name": "Ana",
      "path": "C:\temp"
    }
    """;
// The 4 spaces before the closing """ are removed from every line.

Nếu nội dung chứa """, hãy mở và đóng bằng bốn dấu nháy. Raw literal cũng kết hợp được với nội suy ($"""..."""); xem nội suy chuỗi.

Null, rỗng và khoảng trắng

Một biến chuỗi có thể là null (không có chuỗi nào), rỗng ("", độ dài 0), hoặc chỉ chứa khoảng trắng. Hai phương thức tĩnh kiểm tra những trường hợp này mà không có nguy cơ gặp NullReferenceException:

Đầu ra:

null    empty: True   blank: True
""      empty: True   blank: True
"   "   empty: False  blank: True
"Ana"   empty: False  blank: False
True

Để kiểm tra dữ liệu người dùng nhập vào, string.IsNullOrWhiteSpace gần như luôn là lựa chọn bạn cần. string.Empty và "" là cùng một giá trị; viết cách nào là tùy phong cách.

Duyệt qua các ký tự

Chuỗi có thể duyệt (enumerable), nên foreach đi qua từng char. Kiểu char có các phương thức tĩnh để phân loại ký tự:

Đầu ra:

letters=6 digits=4 upper=1 symbols=1
!6202tesnuS

Nối chuỗi

+ nối chuỗi, và chấp nhận mọi kiểu ở phía bên kia (nó gọi ToString()). Khi kết hợp nhiều giá trị, nên dùng nội suy hoặc string.Join:

Đầu ra:

Ana Silva, 31
Ana Silva, 31
AnaSilva
tea | coffee | juice
Total: 23
Total: 5

Hai dòng cuối cho thấy một cái bẫy về độ ưu tiên: + được tính từ trái sang phải, nên một khi vế trái là chuỗi, mọi dấu + tiếp theo đều là phép nối chuỗi. Hãy đặt phép tính số học trong dấu ngoặc khi nằm giữa một phép nối chuỗi.

Câu hỏi thường gặp

Làm sao lấy độ dài của chuỗi trong C#?

Dùng thuộc tính Length: "hello".Length bằng 5. Đây là thuộc tính, không phải phương thức, nên không có dấu ngoặc. Length đếm số đơn vị mã UTF-16, nên một emoji hay ký tự khác nằm ngoài Basic Multilingual Plane được tính là 2. Gọi nó trên một chuỗi null sẽ ném NullReferenceException.

Làm sao so sánh chuỗi trong C#?

a == b so sánh nội dung văn bản và phân biệt chữ hoa chữ thường. Để so sánh bằng nhau không phân biệt hoa thường, dùng string.Equals(a, b, StringComparison.OrdinalIgnoreCase). Để sắp xếp chuỗi, dùng string.Compare(a, b, StringComparison.Ordinal) hoặc một phép so sánh theo văn hóa (culture), kết quả trả về là số âm, số không hoặc số dương.

Làm sao viết chuỗi nhiều dòng trong C#?

Thêm @ trước literal để biến nó thành chuỗi verbatim: chuỗi có thể trải trên nhiều dòng, dấu gạch chéo ngược được giữ nguyên, còn dấu nháy kép được viết là "". Từ C# 11 trở đi, raw string literal bao bởi """ cũng trải được nhiều dòng, tự bỏ phần thụt lề chung và hoàn toàn không cần escape. Bạn cũng có thể nối các dòng bằng \n hoặc Environment.NewLine.

Chuỗi verbatim trong C# là gì?

Là một string literal có tiền tố @, ví dụ @"C:\Users\ana". Các chuỗi escape không được xử lý, nên dấu gạch chéo ngược chỉ là dấu gạch chéo ngược, rất hợp cho đường dẫn Windows và biểu thức chính quy. Escape duy nhất bên trong là "" cho một dấu nháy kép.

string và String trong C# khác nhau thế nào?

Không khác gì. string là từ khóa C# làm bí danh cho kiểu .NET System.String, giống như int là bí danh của System.Int32. Quy ước là dùng string khi khai báo và string.IsNullOrEmpty(...) khi gọi phương thức tĩnh, nhưng cả hai biên dịch ra hoàn toàn giống nhau.

C# hỗ trợ những ký tự escape nào?

\n xuống dòng, \r về đầu dòng, \t tab, \\ dấu gạch chéo ngược, \" dấu nháy kép, \' dấu nháy đơn, \0 ký tự null, \uXXXX một ký tự UTF-16 theo mã hex, \U0001F600 một ký tự theo mã 8 chữ số (trở thành một cặp surrogate), \a cảnh báo, \b backspace, \f form feed, \v tab dọc, và \e (C# 13) ký tự escape. Một chuỗi không hợp lệ như \d là lỗi biên dịch (CS1009), vì vậy biểu thức chính quy thường được viết bằng chuỗi verbatim.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu