8.5. Text Operations#
This section covers the core string operations needed before and during text parsing: cleaning, splitting, searching, replacing, and reading structured records.
8.5.1. Text Basics#
Before parsing text, clean it first.
string raw = " Alice, 93 ";
string trimmed = raw.Trim(); // "Alice, 93"
string upper = trimmed.ToUpper(); // "ALICE, 93"
string lower = trimmed.ToLower(); // "alice, 93"
bool empty = string.IsNullOrWhiteSpace(raw); // false
Console.WriteLine($"trimmed: '{trimmed}'");
Console.WriteLine($"upper: '{upper}'");
Console.WriteLine($"lower: '{lower}'");
Console.WriteLine($"empty: {empty}");
Use these rules:
Call
Trim()before splitting fieldsCall
IsNullOrWhiteSpace()before parsingNormalize case when comparisons should be case-insensitive
8.5.2. Accessing Characters#
string s = "cat";
for (int i = 0; i < s.Length; i++)
{
Console.WriteLine($"{i}: {s[i]}");
}
// Never index a string unless bounds are known to be valid.
8.5.3. String Immutability#
Strings are immutable in C#. All string methods return new strings; the original is unchanged.
string a = "hello";
string b = a.ToUpper(); // b = "HELLO", a is still "hello"
Console.WriteLine($"a = {a}");
Console.WriteLine($"b = {b}");
8.5.4. Splitting and Tokenizing#
Splitting turns one text line into meaningful fields.
string line = "Ada,98,CS";
string[] parts = line.Split(',');
Console.WriteLine(parts[0]); // Ada
Console.WriteLine(parts[1]); // 98
Console.WriteLine(parts[2]); // CS
Input often has extra whitespace — trim each field after splitting:
string line = " Ada , 98 , CS ";
string[] parts = line.Split(',');
for (int i = 0; i < parts.Length; i++)
parts[i] = parts[i].Trim();
foreach (var p in parts)
Console.WriteLine($"'{p}'");
When input may contain repeated delimiters, remove empty entries:
string line = "Ada,,CS";
string[] parts = line.Split(',', StringSplitOptions.RemoveEmptyEntries);
Console.WriteLine(parts.Length); // 2
8.5.4.1. Token Validation#
Validate the number of fields before accessing by index:
string line = "Ada,98,CS";
string[] parts = line.Split(',');
if (parts.Length != 3)
{
Console.WriteLine("Invalid record: expected 3 fields.");
}
else
{
Console.WriteLine($"Name: {parts[0].Trim()}, Score: {parts[1].Trim()}, Major: {parts[2].Trim()}");
}
8.5.5. Search and Replace#
Search and replace is useful for cleanup and normalization pipelines.
string message = "error: invalid input";
bool hasError = message.Contains("error", StringComparison.OrdinalIgnoreCase);
int at = message.IndexOf("invalid", StringComparison.OrdinalIgnoreCase);
Console.WriteLine($"hasError: {hasError}");
Console.WriteLine($"'invalid' starts at index: {at}");
Use StringComparison.OrdinalIgnoreCase for consistent case-insensitive checks.
string raw = "Name: Alice\tScore: 98";
string cleaned = raw.Replace("\t", " ");
Console.WriteLine(cleaned);
8.5.5.1. Safe Replacement Strategy#
Preserve the original text
Create a cleaned copy
Validate the output
Write to a new file first
string original = "Score: N/A";
string updated = original.Replace("N/A", "0");
Console.WriteLine($"original: {original}");
Console.WriteLine($"updated: {updated}");
8.5.6. Structured Lines#
Structured lines are text records with predictable field order.
Example record: Alice,98,CS
Field model:
name (
string)score (
int)major (
string)
static bool TryParseStudent(string line, out string name, out int score, out string major)
{
name = "";
major = "";
score = 0;
if (string.IsNullOrWhiteSpace(line)) return false;
string[] parts = line.Split(',');
if (parts.Length != 3) return false;
name = parts[0].Trim();
major = parts[2].Trim();
return int.TryParse(parts[1].Trim(), out score);
}
string[] records = { "Alice,98,CS", "Bob,abc,Math", "Carol,87,Physics", "" };
foreach (string line in records)
{
if (TryParseStudent(line, out string name, out int score, out string major))
Console.WriteLine($"{name} ({major}) scored {score}");
else
Console.WriteLine($"Skipped malformed line: '{line}'");
}
Footnotes