Skip to main content
ANVISoftware Solutions
Lesson 10 of 62Beginner16 min

Working with Strings

By the end of this lesson

Build, compare and format text, and avoid the cost of repeated concatenation.

A string holds text. The part that surprises people is that a string never changes once it exists.

Every method that looks like an edit — Trim, Replace, ToUpper, Substring — leaves the original alone and hands you a new string. Miss that and you will write code that runs, reports no error, and does nothing.

The single most common string bug
C#
string reference = "inv-2041";

reference.ToUpperInvariant();              // the new string is thrown away
Console.WriteLine(reference);              // inv-2041

reference = reference.ToUpperInvariant();  // keep what came back
Console.WriteLine(reference);              // INV-2041
  • ToUpperInvariant does not touch reference. It creates a second string and returns it.
  • The first call is valid code. It does the work and discards the result, which is why the compiler cannot help you here.
  • Replace, Trim, Substring, Insert and PadLeft all behave the same way. If the result is not assigned or passed on, nothing has happened.

Building text: three approaches

Program.cs
C#
using System.Text;

List<string> skus = new List<string> { "LAMP-01", "STAND-04", "TIDY-09" };

// A fixed number of pieces: interpolation reads best.
string customer = "Priya Sharma";
string city = "Pune";
string label = $"{customer}, {city}";

// Growing text in a loop: this is the version to avoid.
string slow = "";
foreach (string sku in skus)
{
    slow += sku + ",";
}

// Growing text in a loop: one buffer, appended to.
var builder = new StringBuilder();
foreach (string sku in skus)
{
    builder.Append(sku).Append(',');
}
string fast = builder.ToString().TrimEnd(',');

Console.WriteLine(label);
Console.WriteLine(fast);
  • StringBuilder lives in System.Text, which is not one of the namespaces a console project includes for you, so the using directive is needed.
  • The += version creates an entire new string each time round, copying everything written so far. With 10,000 SKUs that is 10,000 strings and the total copying grows with the square of the count.
  • StringBuilder keeps one resizable buffer and only produces a string when you call ToString().
  • Below a handful of pieces, use interpolation. It is clearer, and the compiler assembles it in a single pass rather than one string per join.

Comparing text

C#
string entered = "priya.sharma@example.com";
string stored = "Priya.Sharma@Example.com";

Console.WriteLine(entered == stored);   // False

bool sameAddress = string.Equals(entered, stored, StringComparison.OrdinalIgnoreCase);
Console.WriteLine(sameAddress);         // True

Console.WriteLine(string.IsNullOrWhiteSpace("   "));                     // True
Console.WriteLine(stored.StartsWith("Priya", StringComparison.Ordinal)); // True
  • For strings, == compares the characters rather than asking whether both variables point at the same object. That behaviour is specific to string; most reference types compare identity instead.
  • Case-insensitive matching belongs in the comparison, not in the data. OrdinalIgnoreCase walks both strings character by character and ignores case.
  • IsNullOrWhiteSpace covers null, empty and spaces-only in one call, which is what form validation usually needs.

Formatting for people to read

C#
decimal invoiceTotal = 1240.5m;
DateTime dueDate = new DateTime(2026, 3, 14);
int itemCount = 4;

Console.WriteLine($"Total: {invoiceTotal:N2}");      // Total: 1,240.50
Console.WriteLine($"Due:   {dueDate:yyyy-MM-dd}");   // Due:   2026-03-14
Console.WriteLine($"Items: {itemCount,5}");          // right-aligned across 5 characters

string exportFolder = @"C:\invoices\2026\march";  // backslashes kept as typed
Console.WriteLine(exportFolder);
  • Anything after the colon inside the braces is a format string. N2 means grouped with two decimal places; yyyy-MM-dd is an explicit date layout.
  • A comma before the colon sets a field width, which is how console output gets lined up into columns.
  • The @ prefix stops backslashes being read as escape sequences, which makes file paths and regular expressions readable.
  • Formatting with C for currency follows the machine's culture settings. For output a person reads that is usually right. For a value you store, log or compare, format with CultureInfo.InvariantCulture so it does not shift between machines.

Summary

  • A string never changes; methods that look like edits return a new string you must keep
  • Interpolation for a fixed number of pieces, StringBuilder when text grows in a loop
  • Compare with a StringComparison rather than lower-casing both sides
  • Format strings after the colon control display; use InvariantCulture for stored or compared values
  • IsNullOrWhiteSpace covers the three empty cases real input actually produces

Practice

Attempt each one before opening the solution. Getting it wrong first is how the idea sticks.

Try it yourself

Try it yourself

Turn a list of product names into one comma-separated line using StringBuilder, with no trailing comma.

Then look up string.Join and write the same thing in one line. Decide which you would keep, and when the other would be the better choice.

Show solution

string.Join does the job in one call and puts separators between items rather than after them, so there is no trailing comma to clean up. Keep that version.

StringBuilder earns its place when the pieces are not a plain list: when you append conditionally, mix in formatting, or write thousands of lines to a report.

C#
using System.Text;

List<string> products = new List<string> { "Desk lamp", "Monitor stand", "Cable tidy" };

var builder = new StringBuilder();
foreach (string product in products)
{
    if (builder.Length > 0)
    {
        builder.Append(", ");
    }

    builder.Append(product);
}

Console.WriteLine(builder.ToString());
Console.WriteLine(string.Join(", ", products));   // same output, one line

Think about it

Think about it

A nightly job builds a 200,000-line report and takes about forty minutes. The loop appends each line with +=. Switching to StringBuilder brings it down to seconds.

Why does changing one operator make that much difference?

Show solution

Each += copies the entire text built so far into a brand new string. By line 150,000, every single append is copying several megabytes, so the total copying grows roughly with the square of the line count.

StringBuilder appends into a buffer it reuses, enlarging it occasionally. The work grows roughly in step with the number of appends rather than with their square.

This is the clearest everyday example of the shape of an algorithm mattering more than the speed of the machine it runs on.

Knowledge check

Nothing is recorded and there is no score. The explanation appears either way.

reference.Replace("-", "/"); sits on its own line, and afterwards reference is unchanged. Why?

Saved in this browser only.