Understanding C# String Formatting and Format Specifiers
Overview of String Formatting
C# provides powerful string formatting capabilities through the Format method and composite format strings. This guide covers the various format specifiers and their practical applications.
How Format Methods Process Format Strings
The Format method converts multiple objects into a single formatted string by following these steps:
- Placeholders like
{0},{1}in the format string get replaced with corresponding arguments in order - When the parser encounters a placeholder, it checks if the corresponding argument implements
IFormattable. If it does, theToStringmethod gets called with any format specifier present - If the argument doesn't implement
IFormattable, the method falls back to the type's nativeToString()method, orObject.ToString()as a final option
var result = string.Format("Non-IFormattable: {0}, IFormattable: {1}",
new CustomType(), 42);
Console.WriteLine(result);
Standard Numeric Format Specifiers
| Specifier | Purpose | Example | Output |
|---|---|---|---|
| C | Currency | "{0:C3}", 2000 |
¥2000.000 |
| D | Decimal | "{0:D3}", 2000 |
2000 |
| F | Fixed-point | "{0:F3}", 2000 |
2000.000 |
| N | Number with separators | "{0:N}", 250000 |
250,000 |
| P | Percentage | "{0:P3}", 0.29768 |
29.768 |
| X | Hexadecimal | "{0:X4}", 12 |
C |
String Class Utility Methods
| Method | Description |
|---|---|
Equals(string value) |
Returns true if strings match exactly |
Compare(string a, string b) |
Returns negative, zero, or positive based on comparison |
IndexOf(string value) |
Returns position of first match, or -1 if not found |
LastIndexOf(string value) |
Returns position of last match, or -1 if not found |
Join(string separator, string[] values) |
Concatenates array elements with separator |
Split(char separator) |
Returns string array split by delimiter |
Substring(int start, int length) |
Extracts substring from start position |
ToLower() |
Returns lowercase version |
ToUpper() |
Returns uppercase version |
Trim() |
Removes leading and trailing whitespace |
Currency Formatting (C)
The C specifier converts numbers to currency format based on the current culture. The precision digit controls decimal places.
var amount = 100;
var formatted = $"{amount:C6}";
Console.WriteLine(formatted);
Output displays the currency symbol with six decimal places.
Decimal Formatting (D)
The D specifier converts integers to decimal representation with optional zero-padding.
var value = 100;
var padded = $"{value:D99}";
Console.WriteLine(padded);
This pads the number to 99 digits with leading zeros.
Numeric Separators (N)
The N specifier formats numbers with thousand separators and optional decimal precision.
var largeNumber = 1000000000;
var formatted = $"{largeNumber:N3}";
Console.WriteLine(formatted);
Output shows comma separators and three decimal places.
Percentage Formatting (P)
The P specifier multiplies by 100 and appends the percent symbol.
var ratio = 1;
var percentage = $"{ratio:P0}";
Console.WriteLine(percentage);
Displays as "100%" with zero decimal places.
Zero Placeholder (0)
The 0 specifier maintains minimum digit count, padding with zeros when necessary. Excess digits are preserved.
int value = 100;
var result = $"{value:00000}";
Console.WriteLine(result);
For decimal precision:
int amount = 1000000;
var formatted = $"{amount:00000.00}";
Console.WriteLine(formatted);
Rounding applies when the value exceeds the specified decimal places.
Digit Placeholder (#)
The # specifier reserves positions for optional digits. Leading and trailing zeros get omitted.
var code = 098804;
var clean = $"{code:####}";
Console.WriteLine(clean);
For decimals:
var price = 098804.15;
var result = $"{price:####.#}";
Console.WriteLine(result);
When decimal places exceed format specifiers, rounding occurs. Trailing zeros in the result get trimmed.
var value = 19884.049;
var output = $"{value:####.#}";
Console.WriteLine(output);
This produces "19884". The key difference: # placeholders suppress zeros, while 0 placeholders display them.
Alignment and Padding
Negative width values left-align, positive values right-align. Spaces fill the remaining width.
var data = "666";
var aligned = string.Format("{0,10}", data);
Console.WriteLine($"${aligned}$");
The PadLeft method achieves identical results:
var data = "666";
var padded = data.PadLeft(10);
Console.WriteLine($"${padded}$");
PadLeft supports custom padding characters, whereas alignment placeholders only work with spaces.
Right alignment:
var data = "666";
var aligned = string.Format("{0,-10}", data);
Console.WriteLine($"${aligned}$");
Use PadRight for similar functionality with custom characters.
Scientific Notation (E/e)
The E and e specifiers output exponential notation.
var value = 666;
var scientific = $"{value:e} {value:E}";
Console.WriteLine(scientific);
E produces uppercase output, while e produces lowercase.
Fixed-Point Formatting (F/f)
The F specifier converts to fixed-point decimal representation with configurable precision.
var number = -6666.66;
var result = $"{number:f6} {number:F3}";
Console.WriteLine(result);
Default precision is two decimal places unless explicitly specified.
General Formatting (G/g)
The G specifier selects between fixed-point and scientific notation based on which is more compact. Precision specifiers control significant digits.
Default precision varies by type:
- Byte/SByte: 3 digits
- Int16/UInt16: 5 digits
- Int32/UInt32: 10 digits
- Int64/UInt64: 19 digits
- Single: 7 digits
- Double: 15 digits
- Decimal: 29 digits
Scientific notation only applies when exponent falls outside the -5 to +precision range.
Numeric Separator Formatting (N/n)
The N specifier combines thousand separators with decimal precision. Default shows two decimal places.
var value = -666;
var formatted = $"{value:N} {value:N6}";
Console.WriteLine(formatted);
Round-Trip Formatting (R/r)
The R specifier guarantees that a formatted number can be parsed back to the exact same value. Used exclusively with Single and Double types.
The algorithm first attempts standard format with maximum precision (15 digits for Double, 7 for Single). If parsing fails to recover the original value, higher precision gets used (17 and 9 digits respectively).
Hexadecimal Formatting (X/x)
The X specifier converts integers to hexadecimal representation.
var decimalValue = 255;
var hexUpper = $"{decimalValue:X}";
var hexLower = $"{decimalValue:x}";
Console.WriteLine($"{hexUpper} {hexLower}");
Precision specifiers control minimum digit count with zero-padding.
Date and Time Formatting
Standard Date/Time Format Specifiers
var flags = new[] { "G", "d", "D", "g", "M", "m", "s", "T", "t", "u", "U", "Y", "r", "R", "o", "O", "F", "f" };
var timestamp = DateTime.Now;
foreach (var flag in flags)
{
Console.WriteLine($"{flag} -> {timestamp.ToString(flag)}");
}
Common specifiers:
d: Short date patternD: Long date patternT: Long time patternt: Short time patternY: Year month patterns: ISO 8601 sortable formatu: Universal sortable formato: ISO 8601 round-trip format
Custom date formatting:
var now = DateTime.Now;
var custom = $"{now:yyyy-MM-dd}";
Console.WriteLine(custom);
Custom Date/Time Format Specifiers
| Specifier | Description |
|---|---|
| d | Day without leading zero (1-31) |
| dd | Day with leading zero (01-31) |
| ddd | Abbreviated day name |
| dddd | Full day name |
| f | Tenths of seconds (1 digit) |
| ff | Hundredths (2 digits) |
| fff | Milliseconds (3 digits) |
| ffff | Ten-thousandths (4 digits) |
| M | Month without leading zero (1-12) |
| MM | Month with leading zero (01-12) |
| MMM | Abbreviated month name |
| MMMM | Full month name |
| h | Hour in 12-hour format (1-12) |
| hh | Hour with leading zero (01-12) |
| H | Hour in 24-hour format (0-23) |
| HH | Hour with leading zero (00-23) |
| m | Minutes (0-59) |
| mm | Minutes with leading zero (00-59) |
| s | Seconds (0-59) |
| ss | Seconds with leading zero (00-59) |
| y | Year (1-2 digits) |
| yy | Year with leading zero (2 digits) |
| yyyy | Full year (4 digits) |
| z | UTC offset in hours (no leading zero) |
| zz | UTC offset with leading zero |
| zzz | UTC offset with hours and minutes |
| K | Timezone kind indicator |
The F variant differs from f by omitting trailing zeros rather than displaying them.