Strings
A string is a sequence of characters — names, messages, file contents, JSON. C# strings are objects with dozens of useful methods, but they have one defining property: they can never be changed after they're created. This lesson covers creating strings, the different kinds of string literal, indexing, immutability and empty values.
Creating strings
string greeting = "Hello"; string empty = ""; string repeated = new string('=', 12); string fromChars = new string(['C', '#']); Console.WriteLine(greeting); Console.WriteLine($"[{empty}]"); Console.WriteLine(repeated); Console.WriteLine(fromChars);
Hello [] ============ C#
Length and characters
Length is the number of characters. Square brackets read a single char by position, starting at 0; ^1 counts from the end (lesson 30):
string word = "Banana"; Console.WriteLine(word.Length); Console.WriteLine(word[0]); Console.WriteLine(word[word.Length - 1]); Console.WriteLine(word[^1]); Console.WriteLine(word[1..4]); // characters 1, 2 and 3 foreach (char c in word) { Console.Write(c + "-"); } Console.WriteLine();
6 B a a ana B-a-n-a-n-a-
Strings are immutable
You can't change a character inside a string. Methods that seem to modify a string — ToUpper, Replace, Trim — return a new string and leave the original untouched:
string name = "sam"; name[0] = 'S';
Program.cs(2,1): error CS0200: Property or indexer 'string.this[int]' cannot be assigned to -- it is read only
string name = "sam"; name.ToUpper(); // result thrown away! Console.WriteLine(name); name = name.ToUpper(); // keep the new string Console.WriteLine(name); string title = char.ToUpper(name[0]) + name[1..].ToLower(); Console.WriteLine(title);
sam SAM Sam
Immutability makes strings safe to share between variables and threads. The cost is that building a long string piece by piece creates many temporary strings — lesson 19's StringBuilder solves that.
Joining strings
You can join strings with +, but interpolation is usually clearer: put $ before the quotes and write expressions in braces:
string first = "Ada"; string last = "Lovelace"; int born = 1815; string a = first + " " + last + " was born in " + born + "."; string b = $"{first} {last} was born in {born}."; string c = string.Concat(first, " ", last); string d = string.Join(", ", "red", "green", "blue"); Console.WriteLine(a); Console.WriteLine(b); Console.WriteLine(c); Console.WriteLine(d); Console.WriteLine($"{first.Length + last.Length} letters, initials {first[0]}{last[0]}");
Ada Lovelace was born in 1815. Ada Lovelace was born in 1815. Ada Lovelace red, green, blue 11 letters, initials AL
Verbatim strings
Prefix a string with @ and backslashes are no longer escape characters. That's convenient for Windows paths and regular expressions. To include a double quote, write it twice:
string path1 = "C:\\Users\\mira\\notes.txt"; string path2 = @"C:\Users\mira\notes.txt"; string quote = @"She said ""hello""."; Console.WriteLine(path1 == path2); Console.WriteLine(path2); Console.WriteLine(quote);
True C:\Users\mira\notes.txt She said "hello".
Raw string literals
For text containing quotes, backslashes or several lines — JSON, SQL, HTML — use a raw string literal: three (or more) double quotes. Nothing inside is escaped, and the indentation of the closing quotes is removed from every line:
string json = """ { "name": "Widget", "path": "C:\temp", "tags": ["new", "sale"] } """; Console.WriteLine(json);
{
"name": "Widget",
"path": "C:\temp",
"tags": ["new", "sale"]
}Raw strings can be interpolated too. Add one $ per brace you want to use for values — with $$, single braces stay literal and {{ }} marks a value:
string product = "Widget"; decimal price = 9.5m; string json = $$""" { "name": "{{product}}", "price": {{price}} } """; Console.WriteLine(json);
{ "name": "Widget", "price": 9.5 }Comparing strings
== compares the characters, case-sensitively. For other comparisons, pass a StringComparison:
string a = "Hello"; string b = "hello"; Console.WriteLine(a == b); Console.WriteLine(a.Equals(b, StringComparison.OrdinalIgnoreCase)); Console.WriteLine(string.Compare("apple", "banana", StringComparison.Ordinal) < 0); Console.WriteLine("Zebra".CompareTo("apple"));
False True True 1
Compare and CompareTo return a negative number, zero or a positive number for "before", "equal" and "after" — the basis of sorting. Ordinal compares character codes and is fast and predictable; culture-aware comparison (the default for CompareTo) follows the alphabet rules of a language, which is what you want when sorting names for display.
Empty, whitespace and null
A string variable can hold text, the empty string "", or no string at all (null, if declared as string?). Two helper methods test for "nothing useful":
string?[] values = ["text", "", " ", null]; foreach (string? v in values) { Console.WriteLine($"[{v}] empty: {string.IsNullOrEmpty(v)}, blank: {string.IsNullOrWhiteSpace(v)}"); }
[text] empty: False, blank: False [] empty: True, blank: True [ ] empty: False, blank: True [] empty: True, blank: True
IsNullOrWhiteSpace is the right check for form fields and user input. string.Empty is another way to write "".
Unicode and Length
A char is a 16-bit UTF-16 code unit. Most characters fit in one, but emoji and some rarer characters need two, so Length counts code units, not what a reader would call characters:
string word = "café"; string emoji = "👍"; Console.WriteLine(word.Length); Console.WriteLine(emoji.Length); Console.WriteLine(new System.Globalization.StringInfo(emoji).LengthInTextElements);
4 2 1
For everyday text this rarely matters, but keep it in mind when truncating strings for display.
Try it yourself
Build initials and a username from a full name.
Initials: GBH Username: grace.hopper Name length: 21