S9.0 What This Teaches
- String interpolation:
$"..." - Composite format strings:
string.Format - Standard numeric format specifiers:
N,C,F,D,X,E,P - Alignment and field width
- Custom format strings
IFormattableandIFormatProvider
S9.1 String Interpolation
$"..." embeds expressions directly in a
string literal. Expressions go inside {} and may include format
specifiers after a colon:
// Formatting - string interpolation examples.
string name = "Alice";
int age = 30;
double score = 0.9875;
Console.WriteLine($"Name: {name}, Age: {age}");
Console.WriteLine($"Score: {score:P1}"); // 98.8%
Console.WriteLine($"Score: {score:F4}"); // 0.9875
Console.WriteLine($"Pi: {Math.PI:F2}"); // 3.14
// Alignment: positive = right-align, negative = left-align
Console.WriteLine($"{"Item",-10} {"Price",8}");
Console.WriteLine($"{"Apple",-10} {1.5,8:C}");
Console.WriteLine($"{"Banana",-10} {0.75,8:C}");
string.Format and ToString.
S9.2 Composite Format Strings
string.Format uses indexed placeholders {0},
{1}, etc. It is the foundation that interpolation compiles to:
string result = string.Format("Hello, {0}! You scored {1:F1}%.", "Bob", 87.5);
Console.WriteLine(result); // Hello, Bob! You scored 87.5%.
// Console.WriteLine accepts composite format directly
Console.WriteLine("X={0}, Y={1}, Z={2}", 1, 2, 3);
// Same placeholder used twice
Console.WriteLine("{0} * {0} = {1}", 7, 49);
S9.3 Standard Numeric Format Specifiers
int n = 1234567;
double d = 12345.6789;
// N - number with thousands separator
Console.WriteLine(n.ToString("N0")); // 1,234,567
Console.WriteLine(d.ToString("N2")); // 12,345.68
// C - currency
Console.WriteLine(d.ToString("C")); // $12,345.68 (locale-dependent)
// F - fixed-point
Console.WriteLine(d.ToString("F3")); // 12345.679
// D - decimal integer (integer types only)
Console.WriteLine(n.ToString("D8")); // 01234567 (zero-padded)
// X - hexadecimal
Console.WriteLine(n.ToString("X")); // 12D687
Console.WriteLine(n.ToString("x8")); // 0012d687 (lowercase, padded)
// E - scientific notation
Console.WriteLine(d.ToString("E2")); // 1.23E+004
// P - percentage
Console.WriteLine(0.1234.ToString("P1")); // 12.3%
S9.4 Alignment and Field Width
{}, a comma followed by a number sets the minimum field width.
Positive aligns right; negative aligns left:
// {index,width:format}
string header = string.Format("{0,-12} {1,8} {2,10}", "Product", "Qty", "Price");
Console.WriteLine(header);
string[] products = { "Widget", "Gadget", "Thingamajig" };
int[] qtys = { 100, 25, 3 };
double[] prices = { 9.99, 49.95, 129.00 };
for (int i = 0; i < products.Length; i++)
Console.WriteLine(string.Format("{0,-12} {1,8} {2,10:C}", products[i], qtys[i], prices[i]));
Product Qty Price
Widget 100 $9.99
Gadget 25 $49.95
Thingamajig 3 $129.00
S9.5 Custom Format Strings
0 (digit or zero), # (digit or empty),
. (decimal point), , (thousands):
double value = 1234.5;
Console.WriteLine(value.ToString("0,000.00")); // 1,234.50
Console.WriteLine(value.ToString("#,##0.##")); // 1,234.5
Console.WriteLine(value.ToString("000.000")); // 1234.500 - leading zeros
// Date formatting
DateTime now = DateTime.Now;
Console.WriteLine(now.ToString("yyyy-MM-dd")); // 2025-04-01
Console.WriteLine(now.ToString("ddd, dd MMM yyyy")); // Tue, 01 Apr 2025
Console.WriteLine(now.ToString("HH:mm:ss")); // 14:32:05
S9.6 IFormattable on Custom Types
IFormattable so your types support format specifiers in
interpolated strings and string.Format:
record Point(double X, double Y) : IFormattable
{
public string ToString(string? format, IFormatProvider? provider)
{
return format switch
{
"F2" => $"({X:F2}, {Y:F2})",
"E" => $"({X:E}, {Y:E})",
_ => $"({X}, {Y})"
};
}
}
var p = new Point(1.23456, 7.89012);
Console.WriteLine($"{p}"); // (1.23456, 7.89012)
Console.WriteLine($"{p:F2}"); // (1.23, 7.89)
Console.WriteLine($"{p:E}"); // (1.234560E+000, 7.890120E+000)
S9.7 Example - All Together
// Formatting - tabular sales report with alignment and numeric specifiers.
var sales = new[]
{
(Region: "North", Units: 1500, Revenue: 74250.0),
(Region: "South", Units: 820, Revenue: 41000.0),
(Region: "East", Units: 2300, Revenue: 115000.0),
(Region: "West", Units: 1100, Revenue: 55500.0),
};
Console.WriteLine($"{"Region",-8} {"Units",8} {"Revenue",14} {"Avg/Unit",12}");
Console.WriteLine(new string('-', 44));
foreach (var (region, units, revenue) in sales)
{
double avg = revenue / units;
Console.WriteLine($"{region,-8} {units,8:N0} {revenue,14:C} {avg,12:C}");
}
double totalRev = sales.Sum(s => s.Revenue);
Console.WriteLine(new string('-', 44));
Console.WriteLine($"{"Total",-8} {sales.Sum(s => s.Units),8:N0} {totalRev,14:C}");
S9.8 Exercise
Exercise
- Format the number 1234567.891 five ways: as an integer with thousands separators, as currency, as fixed-point with 3 decimals, in scientific notation, and as a percentage of 2,000,000.
- Print a 10-row multiplication table using alignment so every column is exactly 5 characters wide and right-aligned.
- Implement
IFormattableon aTemperaturerecord that stores a value in Celsius. Support format"F"for Fahrenheit,"K"for Kelvin, and default for Celsius.
S9.9 Common Mistakes
Currency format is locale-dependent
// On a US machine: $1,234.57
// On a German machine: 1.234,57 €
Console.WriteLine(1234.567.ToString("C"));
// Fix: specify the culture explicitly
using System.Globalization;
Console.WriteLine(1234.567.ToString("C", CultureInfo.InvariantCulture)); // ¤1,234.57
Console.WriteLine(1234.567.ToString("C", new CultureInfo("en-US"))); // $1,234.57
Using D format on a double
double d = 42.0;
// d.ToString("D3") throws FormatException - D is only for integer types!
int n = 42;
n.ToString("D3"); // "042" - correct
String concatenation in tight loops instead of StringBuilder
// Slow: creates a new string object each iteration
string result = "";
foreach (var item in items)
result += $"{item}, ";
// Fast: StringBuilder amortizes allocations
var sb = new System.Text.StringBuilder();
foreach (var item in items)
sb.Append($"{item}, ");
string result2 = sb.ToString();
S9.10 Key Terms
| Term | Meaning |
|---|---|
| string interpolation | $"..." syntax; embeds expressions with optional format specifiers |
| composite format | string.Format with {index,width:format} placeholders |
| N | Number with thousands separator and optional decimal places |
| C | Currency; locale-dependent symbol and grouping |
| F | Fixed-point; specifies decimal places |
| X / x | Hexadecimal; uppercase or lowercase digits |
| E | Scientific notation |
| IFormattable | Interface enabling custom types to respond to format specifiers |