Strings and Text Processing

string (alias of System.String) is an immutable sequence of UTF-16 chars.

Concept

String Basics and Immutability string (alias of System.String) is an immutable sequence of UTF-16 chars. Every "modifying" method — ToUpper, Replace, Trim, Substring — returns a new string; the original never changes. This makes strings thread-safe and safe to share, but building a string in a loop with + creates many temporary objects.

Escape sequences: \n, \t, \\, \", \u00B0, \e (C# 13)

Backslashes are literal; "" for a quote; may span lines $"Welcome to {name}"

Expressions inside {}, with format {value:F2} and alignment {text,-8} """{ "ecu": "VCU" }"""

No escaping at all; indentation of the closing quotes is removed $$"""{"name": "{{name}}"}"""

The number of $ sets how many braces start an interpolation

Contains, IndexOf, LastIndexOf, StartsWith, EndsWith

Substring, ranges s[..8], s[(i + 1)..], indexer s[0] Replace, Remove, Insert

Split (with StringSplitOptions), string.Join, string.Concat

Trim, TrimStart, TrimEnd, PadLeft, PadRight ToUpper, ToLower, ToUpperInvariant, ToLowerInvariant

string.IsNullOrEmpty, string.IsNullOrWhiteSpace Comparison and Culture

== compares contents ordinally (character codes), case-sensitively.

string.Equals(a, b, StringComparison.OrdinalIgnoreCase) — the right choice for identifiers, file names, protocol keywords.

Culture-aware comparison (StringComparison.CurrentCulture / InvariantCulture) follows human sorting rules: it puts apple before Banana, while ordinal order puts uppercase letters first.

Casing is culture-sensitive too: in Turkish, "file".ToUpper() is "FİLE" (dotted capital İ). Use ToUpperInvariant for machine data.

Formatting and Culture Format strings: F2 (fixed 2 decimals), N2 (thousands separators), P1 (percent), X4 (hex, 4 digits), date patterns like yyyy-MM-dd, alignment {x,8} / {x,-8}. Formatting uses the current culture — on a machine set to Indian English, 1234567.891:N2 prints 12,34,567.89; with the invariant culture it prints 1,234,567.89. Programs in this course set CultureInfo.CurrentCulture = CultureInfo.InvariantCulture where formatting matters, so output is identical everywhere.

System.Text.StringBuilder is a mutable buffer for building strings efficiently: Append, AppendLine, Insert, Replace, Remove, then ToString().

System.Text.RegularExpressions.Regex matches text patterns:

\d, \w, \s, . digit, word char, whitespace, any char [A-F0-9], [^x] ^, $, \b anchors / word boundary (), (?<name>) capturing groups / named groups $1, ${name} references in replacements

APIs: IsMatch (validation), Match / Matches (extraction; Groups, Captures), Replace, Split. Always pass a timeout for untrusted input — nested quantifiers like (a+)+ can backtrack catastrophically. For hot paths, use RegexOptions.Compiled or the [GeneratedRegex] source generator (Module 43).

Why It Matters

Logs, protocols, configuration, user input and reports are text. Correct comparison and culture handling prevent subtle bugs (the "Turkish I" problem, comma vs dot decimals), and StringBuilder and regex timeouts prevent performance problems.

Syntax

string s = $"{value:F2} {text,-10}";
bool same = string.Equals(a, b, StringComparison.OrdinalIgnoreCase);
var sb = new StringBuilder().Append("x").AppendLine();
Match m = Regex.Match(input, @"(?<id>[0-9A-F]{8})");

Example

9.2 Regular expressions

using System.Globalization;
using System.Text;

// Make number/date formatting identical on every machine (see "Culture" below)
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;

string name = "SampleApp";

// Creation, interpolation, verbatim and raw literals, escapes
string message = $"Welcome to {name}";
string path = @"C:\logs\can\trace.asc";                 // verbatim: backslashes are literal
string tab = "col1\tcol2";                              // escape sequence
string json = """
    {
      "ecu": "VCU",
      "voltage": 12.6
    }
    """;                                                // raw string literal (C# 11)
string rawInterp = $$"""{"name": "{{name}}"}""";       // raw + interpolation
Console.WriteLine(message);
Console.WriteLine(path);
Console.WriteLine(tab);
Console.WriteLine(json);
Console.WriteLine(rawInterp);

// Immutability: methods return NEW strings
string upper = name.ToUpper();
Console.WriteLine($"original: {name}, upper: {upper}, lower: {name.ToLowerInvariant()}");

// Searching, slicing, replacing, splitting, joining, trimming
string frame = "  18FEF100#0102030405060708  ";
string t = frame.Trim();
int hash = t.IndexOf('#');
Console.WriteLin
…
using System.Text.RegularExpressions;

// Validation
Regex vin = new(@"^[A-HJ-NPR-Z0-9]{17}$");       // 17 chars, no I, O, Q
foreach (string v in new[] { "EHBAUREV250000001", "EHBAUREV25000000I", "SHORT" })
    Console.WriteLine($"{v,-18} valid VIN: {vin.IsMatch(v)}");

// Matching with groups and named groups
string log = "12.500 CAN1 18FEF100 Rx d 8 01 02 03 04 05 06 07 08; 12.510 CAN1 0CF00400 Rx d 8 FF 00 7D 00 00 00 00 00";
Regex frame = new(@"(?<time>\d+\.\d+) CAN(?<ch>\d) (?<id>[0-9A-F]{8}) Rx d (?<dlc>\d)");
foreach (Match m in frame.Matches(log))
    Console.WriteLine($"t={m.Groups["time"].Value} ch={m.Groups["ch"].Value} id=0x{m.Groups["id"].Value} dlc={m.Groups["dlc"].Value}");

// Captures and replacement
string dtcs = "P0A80, U0100 and C1234; also p0300";
MatchCollection codes = Regex.Matches(dtcs, @"\b[PCBU][0-3][0-9A-F]{3}\b", RegexOptions.IgnoreCase);
Console.WriteLine($"DTCs: {string.Join(" ", codes.Select(c => c.Value.ToUpperInvariant()))}");
string masked = Regex.Replace("VIN EHBAUREV250000001 reported", @"([A-Z0-9]{11})([0-9]{6})", "$1******");
Console.WriteLine(masked);

// Timeout protects against catastrophic backtracking
try
{
    var evil = new Regex(@"^(a
…

Expected Result

Regular expressions:

The pattern ^(a+)+$ against 40 as followed by ! would take astronomically long without the timeout.

Welcome to SampleApp
C:\logs\can\trace.asc
col1	col2
{
  "ecu": "VCU",
  "voltage": 12.6
}
{"name": "SampleApp"}
original: SampleApp, upper: SAMPLEAPP, lower: sampleapp
trimmed: '18FEF100#0102030405060708', length 25, '#' at 8
id = 18FEF100, data = 0102030405060708, contains "0304": True, starts with 18: True
replace: 18FEF100 | 0102030405060708
split: [VCU] [BMS] [ABS] (3 parts)
PadLeft: '007', Substring(0, 6): Sample
"abc" == "ABC": False, OrdinalIgnoreCase: True
Compare("apple", "Banana") ordinal: 1, invariant culture: -1
"file".ToUpper(tr-TR) == "FILE": False, ToUpperInvariant: FILE
12.35 V | 25.6 % | 1,234,567.89 | 00FF | 1970-01-01 | [left    ] [   right]
>> Hello C# 14 1 2 3 (length 20)
EHBAUREV250000001  valid VIN: True
EHBAUREV25000000I  valid VIN: False
SHORT              valid VIN: False
t=12.500 ch=1 id=0x18FEF100 dlc=8
t=12.510 ch=1 id=0x0CF00400 dlc=8
DTCs: P0A80 U0100 C1234 P0300
VIN EHBAUREV250****** reported
RegexMatchTimeoutException: pattern took longer than 100 ms

Common Mistakes

Concatenating in a loop with += — use StringBuilder or string.Join.

Forgetting that Replace, Trim, etc. return a new string: s.Trim(); alone does nothing.

Comparing with ToLower() == — allocates and is culture-sensitive; use StringComparison.OrdinalIgnoreCase.

Using culture-sensitive formatting/parsing for files and protocols.

Regex without a timeout on user input (ReDoS).

Writing a pattern that is "almost right" — for example \d{4} for DTC codes misses P0A80, because the last three characters are hexadecimal: [PCBU][0-3][0-9A-F]{3}.

Best Practices

Use interpolation for readability, StringBuilder for loops, raw literals for JSON/SQL/regex text.

Specify StringComparison explicitly (analyzers CA1307/CA1309 can enforce it).

Use invariant culture for machine data and current culture only for display.

Keep regexes readable: named groups, comments with RegexOptions.IgnorePatternWhitespace, and unit tests.

Interview Relevance

Very high: "Why are strings immutable?", "string vs StringBuilder?", "== vs Equals for strings?", "reverse a string / check a palindrome / count words", "what is string interning?".

Practice Questions

Count the vowels in a sentence.

Reverse each word of a sentence but keep word order.

Parse "18FEF100#0102030405060708" into an ID uint and a byte[].

Validate an email address with a (simple) regex and a timeout.

Build a 1 000-line CSV with StringBuilder and measure its length.

Quick Quiz

Q1. (Predict the output) string s = " ecu "; s.Trim(); Console.WriteLine($"[{s}]");

Answer: [ ecu ] — Trim returns a new string that was discarded.

Q2. (Multiple choice) Which comparison should you use for file names and identifiers? A) CurrentCulture B) InvariantCulture C) OrdinalIgnoreCase D) ToLower() == Answer: C.

Q3. (True/False) "abc" == "ABC" is true in C#.

Answer: False. == on strings is ordinal and case-sensitive (verified).

Q4. (Predict the output) Console.WriteLine($"{255:X4}"); Answer: 00FF (verified).

Coding Challenge

Write bool IsPalindrome(string s) that ignores case, spaces and punctuation ("A man, a plan, a canal: Panama" → true).

static bool IsPalindrome(string s)
{
    var letters = s.Where(char.IsLetterOrDigit).Select(char.ToLowerInvariant).ToArray();
    for (int i = 0, j = letters.Length - 1; i < j; i++, j--)
        if (letters[i] != letters[j]) return false;
    return true;
}
Console.WriteLine(IsPalindrome("A man, a plan, a canal: Panama"));
Console.WriteLine(IsPalindrome("CAN bus"));