Overview
Efficient string handling is critical for application performance. Understanding when to use strings vs StringBuilder, proper comparison methods, and formatting options helps write robust and performant code.
Core Concepts
String Immutability
// Every "modification" creates a new string
string original = "Hello";
string modified = original + " World"; // Creates new string
// original is unchanged
Console.WriteLine(original); // "Hello"
Console.WriteLine(modified); // "Hello World"
// This is why string concatenation in loops is expensive
string result = "";
for (int i = 0; i < 1000; i++)
{
result += i.ToString(); // Creates 1000 new strings!
}
// Better: Use StringBuilder
var sb = new StringBuilder();
for (int i = 0; i < 1000; i++)
{
sb.Append(i); // Modifies internal buffer
}
string betterResult = sb.ToString(); // Single string creation
StringBuilder Best Practices
// Pre-allocate capacity if size is known
var sb = new StringBuilder(1000); // Avoids resizing
// Chained operations
var result = new StringBuilder()
.Append("Name: ")
.AppendLine("Alice")
.Append("Age: ")
.Append(25)
.ToString();
// Insert and replace
sb.Clear();
sb.Append("Hello World");
sb.Insert(5, " Beautiful"); // "Hello Beautiful World"
sb.Replace("World", "C#"); // "Hello Beautiful C#"
// When to use StringBuilder:
// - Concatenating in loops
// - Building strings with many parts
// - When final string size is unknown
// - Generally: 4+ concatenations
// When regular strings are fine:
// - Simple concatenation: a + b + c
// - String interpolation: $"{a} {b}"
// - Single operations
String Comparison
// Case-sensitive comparison (default)
bool equal = "Hello" == "hello"; // false
// Case-insensitive comparison
bool equalIgnoreCase = string.Equals("Hello", "hello",
StringComparison.OrdinalIgnoreCase); // true
// Comparison types:
// - Ordinal: byte-by-byte comparison (fastest)
// - OrdinalIgnoreCase: ordinal + case folding
// - CurrentCulture: uses current culture rules
// - InvariantCulture: culture-independent linguistic rules
// For user-facing text (sorting, display)
int compareResult = string.Compare("cafe", "café",
StringComparison.CurrentCulture);
// For identifiers, paths, keys (technical comparison)
int ordinalCompare = string.Compare("ABC", "abc",
StringComparison.OrdinalIgnoreCase);
// StartsWith/EndsWith with comparison
bool starts = "Hello".StartsWith("HE",
StringComparison.OrdinalIgnoreCase); // true
String Formatting
// String interpolation (preferred)
decimal price = 19.99m;
DateTime date = DateTime.Now;
string message = $"Price: {price:C2}, Date: {date:yyyy-MM-dd}";
// Format specifiers
int number = 42;
Console.WriteLine($"{number:D5}"); // "00042" (padded)
Console.WriteLine($"{number:X}"); // "2A" (hex)
Console.WriteLine($"{number:N0}"); // "42" (with grouping)
decimal money = 1234.56m;
Console.WriteLine($"{money:C}"); // "$1,234.56" (currency)
Console.WriteLine($"{money:F2}"); // "1234.56" (fixed point)
DateTime now = DateTime.Now;
Console.WriteLine($"{now:yyyy-MM-dd}"); // "2024-01-15"
Console.WriteLine($"{now:HH:mm:ss}"); // "14:30:45"
Console.WriteLine($"{now:dddd, MMMM d}"); // "Monday, January 15"
// Alignment and padding
string name = "Alice";
Console.WriteLine($"|{name,10}|"); // "| Alice|" (right-aligned)
Console.WriteLine($"|{name,-10}|"); // "|Alice |" (left-aligned)
// Composite formatting (older style)
string formatted = string.Format("Name: {0}, Age: {1}", "Bob", 30);
Parsing and Conversion
// Parse (throws on failure)
int parsed = int.Parse("42");
double doubleVal = double.Parse("3.14");
// TryParse (safe, returns bool)
if (int.TryParse("42", out int result))
{
Console.WriteLine(result);
}
// ToString conversions
int num = 255;
string decStr = num.ToString(); // "255"
string hexStr = num.ToString("X"); // "FF"
string paddedStr = num.ToString("D5"); // "00255"
// Culture-specific parsing
string germanPrice = "1.234,56"; // German format
if (decimal.TryParse(germanPrice,
NumberStyles.Currency,
new CultureInfo("de-DE"),
out decimal price))
{
Console.WriteLine(price); // 1234.56
}
Common String Operations
// Substring
string text = "Hello, World!";
string sub = text.Substring(0, 5); // "Hello"
string sub2 = text[7..12]; // "World" (range syntax)
// Split with options
string csv = "a,,b,c,";
string[] parts = csv.Split(',',
StringSplitOptions.RemoveEmptyEntries); // ["a", "b", "c"]
// String.IsNullOrEmpty vs String.IsNullOrWhiteSpace
string? nullStr = null;
string empty = "";
string whitespace = " ";
string.IsNullOrEmpty(nullStr); // true
string.IsNullOrEmpty(empty); // true
string.IsNullOrEmpty(whitespace); // false
string.IsNullOrWhiteSpace(nullStr); // true
string.IsNullOrWhiteSpace(empty); // true
string.IsNullOrWhiteSpace(whitespace); // true
// Null-safe operations
string? maybe = GetPossiblyNullString();
int length = maybe?.Length ?? 0;
string safe = maybe ?? "default";
Interview Tips
Tip 1: Always mention string immutability when discussing string operations. This shows understanding of memory implications.
Tip 2: Know when to use StringBuilder: generally for 4+ concatenations or concatenation in loops.
Tip 3: For comparison questions, distinguish between ordinal (technical) and culture-aware (user-facing) comparisons.
Common Interview Questions
-
Why are strings immutable in C#?
- Thread safety (no synchronization needed), string interning for memory efficiency, security (strings can't be modified after validation), and hashcode caching. Immutability also enables strings to be used as dictionary keys safely.
-
When should you use StringBuilder instead of string concatenation?
- When concatenating in loops, building strings with many parts, or when final size is unknown. Rule of thumb: 4+ concatenations. Each string concatenation creates a new string object, causing memory allocations and copies.
-
What's the difference between == and Equals() for strings?
- In C#, they behave the same for strings due to operator overloading - both compare values. However, Equals() allows specifying StringComparison for case-insensitivity. Use
string.Equals(a, b, StringComparison...)for explicit comparison control.
- In C#, they behave the same for strings due to operator overloading - both compare values. However, Equals() allows specifying StringComparison for case-insensitivity. Use
-
What is string interning?
- String interning stores one copy of each unique string literal in the intern pool. Identical string literals reference the same memory.
string.Intern()explicitly interns runtime strings. Useful for memory optimization with repeated strings.
- String interning stores one copy of each unique string literal in the intern pool. Identical string literals reference the same memory.
-
How do you compare strings case-insensitively?
- Use
string.Equals(a, b, StringComparison.OrdinalIgnoreCase)ora.Equals(b, StringComparison.OrdinalIgnoreCase). AvoidToLower()/ToUpper()comparison as it creates new strings and has culture issues.
- Use
-
What's the difference between String.Empty and ""?
- They're equivalent; both reference the same interned empty string instance.
String.Emptyis slightly more readable and avoids magic string appearance. Performance is identical.
- They're equivalent; both reference the same interned empty string instance.
-
How do you safely handle potentially null strings?
- Use
string.IsNullOrEmpty()orstring.IsNullOrWhiteSpace(). Use null-conditional operator?.and null-coalescing??. With C# 8+ nullable reference types, annotate withstring?.
- Use