C# (.NET 8 LTS) track

Lesson 17 · Part 4: Exceptions & I/O

String Handling

Process text with string methods, interpolation, StringBuilder and regular expressions.

Part 4: Exceptions & I/O5 min readFree lesson

Neatly processing the read text is necessary in almost all programs. string in C# provides a wealth of methods, including StringBuilder for iterative composition and Regex for pattern matching. We also point out the pitfall that the Number/Date parsing results may vary depending on culture.

Learning Objectives

  • Know key methods such as Trim/Split/Join/Replace/ToUpper/Contains/StartsWith
  • Utilizes the interpolation string $"..." and format specifiers (F2, yyyy-MM-dd, etc.)
  • I know that StringBuilder is fast for iterative synthesis.
  • Learn the basic usage of Regex.IsMatch/Match/Matches
  • Understand that parsing results may vary depending on CultureInfo

Key concepts

1) Common string methods

"  hello  ".Trim();          // "hello"
"a,b,c".Split(',');          // ["a", "b", "c"]
string.Join("-", ["a","b"]); // "a-b"
"abc".Replace("b", "X");     // "aXc"
"hi".ToUpper();              // "HI"
"hello".Contains("ll");      // true
"hello".StartsWith("he");    // true

string is immutable (Part 02), so all methods return a new string.

2) Interpolation string $"..."

This is the cleanest synthesis method that inserts variables and expressions as is.

double pi = 3.14159;
DateTime now = DateTime.Now;

Console.WriteLine($"Pi is about {pi:F2}");           //2 decimal places
Console.WriteLine($"Today is {now:yyyy-MM-dd}");
Console.WriteLine($"Sort: |{42,5}|");              //Width 5, right aligned

The format specifier follows the colon. F2 is 2 decimal places, and yyyy-MM-dd is the ISO date.

3) StringBuilder — building text iteratively

If you accumulate strings with += inside a loop, you will get a new object each time. As the number of times increases, it becomes very slow. StringBuilder is fast because it is concatenated to an internal buffer.

using System.Text;

var sb = new StringBuilder();
for (int i = 0; i < 1000; i++)
    sb.Append(i).Append(',');

string result = sb.ToString();

For synthesis less than a dozen times, just + is fine. If repeated hundreds to thousands of times, StringBuilder.

4) Regex — regular expressions

Regular expression in the System.Text.RegularExpressions namespace.

using System.Text.RegularExpressions;

Regex.IsMatch("hello123", @"\d+");           //true (includes numbers?)

var m = Regex.Match("Spell #4242", @"\d+");
if (m.Success) Console.WriteLine(m.Value);    // "4242"

foreach (Match x in Regex.Matches("a1 b22 c333", @"\d+"))
    Console.WriteLine(x.Value);               // 1, 22, 333

@"..." is a verbatim string that accepts backslashes — works well with regular expressions.

5) Culture Trap

Parsing of numbers or dates is affected by system culture. For example, in Germany , is the decimal point.

using System.Globalization;

//Korea, UK, etc.: “3.14” is 3.14
//Germany (de-DE): "3.14" is interpreted as 314, and "3,14" is interpreted as 3.14
decimal de = decimal.Parse("3,14", CultureInfo.GetCultureInfo("de-DE"));
decimal inv = decimal.Parse("3.14", CultureInfo.InvariantCulture);

Like-Settings/CSV/Log The machine-readable format always specifies CultureInfo.InvariantCulture. Use system culture only for user screen display.

See as example

Example 1 — StringMethods: Frequently used methods

string s = "  Hello, C# World  ";

Console.WriteLine($"Original: [{s}]");
Console.WriteLine($"Trim    : [{s.Trim()}]");
Console.WriteLine($"Upper   : [{s.Trim().ToUpper()}]");
Console.WriteLine($"Replace : [{s.Trim().Replace("C#", "DotNet")}]");

string[] parts = "Apples, bananas, cherries".Split(',');
Console.WriteLine($"Split: {parts.Length} → {string.Join(" | ", parts)}");

Console.WriteLine($"Contains 'C#'   : {s.Contains("C#")}");
Console.WriteLine($"StartsWith ' ' : {s.StartsWith(" ")}");

Execution result

Original: [Hello, C# World]
Trim    : [Hello, C# World]
Upper   : [HELLO, C# WORLD]
Replace : [Hello, DotNet World]
Split: 3 → apple | Banana | cherry
Contains 'C#'   : True
StartsWith ' ' : True

Note: You can chain transformations using method chaining.

Example 2 — Interpolation: Interpolation string and format

double pi = 3.14159265;
int score = 87;
DateTime now = new DateTime(2025, 5, 18, 14, 30, 0);

Console.WriteLine($"pi: {pi:F2}");
Console.WriteLine($"Score: {score,5} points");      //Width 5, right aligned
Console.WriteLine($"Score (left): {score,-5}|");       //Negative Width → Left Align
Console.WriteLine($"Date: {now:yyyy-MM-dd HH:mm}");
Console.WriteLine($"Percent: {0.873:P1}");        //1-digit percentage

Execution result

Pi: 3.14
Score: 87 points
Score (left): 87 |
Date: 2025-05-18 14:30
Percent: 87.3%

Note: ,N is the width, :F2 is the format, and if both are used together, it becomes {value,10:F2}.

Example 3 — StringBuilderUse: Iterative synthesis performance

using System.Text;

var sb = new StringBuilder();
sb.Append("[");
for (int i = 1; i <= 5; i++)
{
    if (i > 1) sb.Append(", ");
    sb.Append(i);
}
sb.Append("]");

Console.WriteLine(sb.ToString());
Console.WriteLine($"Length: {sb.Length}");
//Performance note: If you have a few hundred loops or more, this is very fast compared to +=

Execution result

[1, 2, 3, 4, 5]
Length: 15

Note: For short synthesis, + is sufficient, but the more iterations, the greater the benefit of StringBuilder.

Example 4 — RegexBasics: Regular expression basics

using System.Text.RegularExpressions;

string text = "Please send inquiries to [email protected] or [email protected].";

//1) Included or not
bool hasDigit = Regex.IsMatch("Spell #42", @"\d+");
Console.WriteLine($"Include numbers? {hasDigit}");

//2) First match
Match first = Regex.Match("Price 1500 won", @"\d+");
Console.WriteLine($"First number: {first.Value}");

//3) Extract all emails
foreach (Match m in Regex.Matches(text, @"[\w\.-]+@[\w\.-]+\.\w+"))
{
    Console.WriteLine($"Mail: {m.Value}");
}

Execution result

Include numbers? True
First number: 1500
Email: [email protected]
Email: [email protected]

Note: Practical email regular expressions are much more complex — these are simple patterns for learning.

Example 5 — Culture: Same string, different results

using System.Globalization;

string s = "3,14";

decimal de  = decimal.Parse(s, CultureInfo.GetCultureInfo("de-DE"));
decimal inv;
bool ok = decimal.TryParse(s, NumberStyles.Number, CultureInfo.InvariantCulture, out inv);

Console.WriteLine($"de-DE interpretation: {de}");           // 3.14
Console.WriteLine($"Invariant interpretation: ok={ok}, value={inv}"); //',' in thousands is 314 or failure.

//Safe machine format
decimal price = 1234.56m;
Console.WriteLine($"Machine Format: {price.ToString(CultureInfo.InvariantCulture)}");

Execution result

de-DE interpretation: 3.14
Invariant interpretation: ok=True, value=314
Machine Format: 1234.56

Memo: Always read and write CSV/configuration files as InvariantCulture. Use user culture only for UI display.

Common Mistakes

  1. Combining strings with += in a loop — If there are many times, use StringBuilder.
  2. Compare user input without Trim — fails due to spaces at both ends.
  3. In the regular expression, escape it twice like \\d — use the verbatim string of @"\d+".
  4. Ignoring culture when parsing numbers — Results vary depending on the operating environment locale. InvariantCulture for data such as CSV/JSON.
  5. Use == for case comparison — string.Equals(a, b, StringComparison.OrdinalIgnoreCase) is safe.

Cleanup

  • Trim/Split/Join/Replace are everyday tools
  • Composite interpolates string $"..." + format specifier
  • If repeated a lot, StringBuilder
  • Pattern matching is Regex.IsMatch/Match/Matches
  • Data format parsing specifies CultureInfo.InvariantCulture

Try it yourself

cd src/StringMethods && dotnet run
cd ../Interpolation && dotnet run
cd ../StringBuilderUse && dotnet run
cd ../RegexBasics && dotnet run
cd ../Culture && dotnet run

Example files

The runnable files used in this lesson. Copy them into your own project and experiment.

src/Culture/Culture.csprojText
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>CodingNow.Lecture.IoEx17</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>
src/Culture/Program.csC#
//Even if the string is the same, the results are different depending on the culture.
using System.Globalization;

string s = "3,14";

decimal de = decimal.Parse(s, CultureInfo.GetCultureInfo("de-DE"));
bool ok = decimal.TryParse(
    s, NumberStyles.Number, CultureInfo.InvariantCulture, out decimal inv);

Console.WriteLine($"de-DE interpretation: {de}");                  // 3.14
Console.WriteLine($"Invariant interpretation: ok={ok}, value={inv}");      //',' in thousands → 314

//The machine-readable format always specifies InvariantCulture.
decimal price = 1234.56m;
Console.WriteLine($"Machine Format: {price.ToString(CultureInfo.InvariantCulture)}");
src/Interpolation/Interpolation.csprojText
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>CodingNow.Lecture.IoEx17</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>
src/Interpolation/Program.csC#
//interpolation string and format specifier
double pi = 3.14159265;
int score = 87;
DateTime now = new DateTime(2025, 5, 18, 14, 30, 0);

Console.WriteLine($"pi: {pi:F2}");           //2 decimal places
Console.WriteLine($"Score: {score,5} points");        //Width 5, right aligned
Console.WriteLine($"Score (left): {score,-5}|");         //negative → left-justified
Console.WriteLine($"Date: {now:yyyy-MM-dd HH:mm}");
Console.WriteLine($"Percent: {0.873:P1}");          //1-digit percentage
src/RegexBasics/Program.csC#
//Regex's IsMatch / Match / Matches
using System.Text.RegularExpressions;

string text = "Please send inquiries to [email protected] or [email protected].";

//1) Included or not
bool hasDigit = Regex.IsMatch("Spell #42", @"\d+");
Console.WriteLine($"Include numbers? {hasDigit}");

//2) First match
Match first = Regex.Match("Price 1500 won", @"\d+");
Console.WriteLine($"First number: {first.Value}");

//3) Extract all emails (simple pattern for learning)
foreach (Match m in Regex.Matches(text, @"[\w\.-]+@[\w\.-]+\.\w+"))
{
    Console.WriteLine($"Mail: {m.Value}");
}
src/RegexBasics/RegexBasics.csprojText
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>CodingNow.Lecture.IoEx17</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>
src/StringBuilderUse/Program.csC#
//StringBuilder: Strong against repeated composition
using System.Text;

var sb = new StringBuilder();
sb.Append("[");
for (int i = 1; i <= 5; i++)
{
    if (i > 1) sb.Append(", ");
    sb.Append(i);
}
sb.Append("]");

Console.WriteLine(sb.ToString());
Console.WriteLine($"Length: {sb.Length}");
//Performance note: If you have a few hundred loops or more, this is very fast compared to +=
src/StringBuilderUse/StringBuilderUse.csprojText
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>CodingNow.Lecture.IoEx17</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>
src/StringMethods/Program.csC#
//Collection of frequently used string methods
string s = "  Hello, C# World  ";

Console.WriteLine($"Original: [{s}]");
Console.WriteLine($"Trim    : [{s.Trim()}]");
Console.WriteLine($"Upper   : [{s.Trim().ToUpper()}]");
Console.WriteLine($"Replace : [{s.Trim().Replace("C#", "DotNet")}]");

string[] parts = "Apples, bananas, cherries".Split(',');
Console.WriteLine($"Split: {parts.Length} → {string.Join(" | ", parts)}");

Console.WriteLine($"Contains 'C#'   : {s.Contains("C#")}");
Console.WriteLine($"StartsWith ' ' : {s.StartsWith(" ")}");
src/StringMethods/StringMethods.csprojText
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>CodingNow.Lecture.IoEx17</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

Assignment

Problem 1 — Word count and longest word

  • Reference solution: shown below
  • Key concepts: Split, repetition, comparison

Requirements

  • Receive one-line sentence input from the user (Console.ReadLine())
  • Separate words with spaces, but exclude blank items
  • Prints the number of words and the longest word (the first one if tied for first place)

Expected output

Enter the sentence: The quick brown fox jumps over the lazy dog
Word Count: 9
Longest word: quick

Hint

  • Remove empty entries with input.Split(' ', StringSplitOptions.RemoveEmptyEntries)
  • or Split(new[]{' '}, StringSplitOptions.RemoveEmptyEntries)
  • To find the longest word, initialize to the first word and then replace it if a longer one appears.

Problem 2 — Extract all emails

  • Reference solution: shown below
  • Key concepts: Regex.Matches, verbatim string

Requirements

  • Find all emails in the following text and print them line by line:

Contact: [email protected], [email protected]. For inquiries, please send to [email protected].

- Regular expression pattern `[\w\.\+-]+@[\w\.-]+\.\w+` is sufficient (for learning)

### Expected output

[email protected] [email protected] [email protected]


### Hint
- `using System.Text.RegularExpressions;`
- `foreach (Match m in Regex.Matches(text, pattern))`
- The body can be embedded as a string constant in the code.

## Check the correct answer
After solving it yourself, compare it with the reference solution below.
Show the reference solution

Try the assignment yourself first — comparing your attempt with a working solution is where most of the learning happens.

Homework01/Homework01.csprojText
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>CodingNow.Lecture.IoEx17</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>
Homework01/Program.csC#
//Find the longest word with word count
Console.Write("Enter a sentence:");
string input = Console.ReadLine() ?? "";

string[] words = input.Split(' ', StringSplitOptions.RemoveEmptyEntries);

if (words.Length == 0)
{
    Console.WriteLine("Input is empty.");
    return;
}

string longest = words[0];
foreach (var w in words)
{
    if (w.Length > longest.Length)
        longest = w;
}

Console.WriteLine($"Word count: {words.Length}");
Console.WriteLine($"Longest word: {longest}");
Homework02/Homework02.csprojText
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>CodingNow.Lecture.IoEx17</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>
Homework02/Program.csC#
//Extract all emails from the text
using System.Text.RegularExpressions;

string text = "Contact information: [email protected], [email protected].\n"
            + "Please send inquiries to admin [email protected].";

string pattern = @"[\w\.\+-]+@[\w\.-]+\.\w+";

foreach (Match m in Regex.Matches(text, pattern))
{
    Console.WriteLine(m.Value);
}

Learn this lesson with a mentor

Work through the C# (.NET 8 LTS) track on your own, or join a guided cohort at our Pitampura campus and online.

Call NowEnroll Now