Every snippet in this post passes on a developer machine set to US English. Then someone in Istanbul, Berlin or London runs it, or data arrives from another system, and it quietly does the wrong thing.
To reproduce that, the demo sets the culture per "user" before each run. Every output below is copied from actually running it on .NET 10.
1. ToUpper() and the Turkish i
string command = "quit"; // what the user typed
if (command.ToUpper() == "QUIT")
Console.WriteLine("Bye!");
else
Console.WriteLine($"Unknown command: {command}");
On a US machine: Bye!. On a machine set to Turkish:
Unknown command: quit
"quit".ToUpper() = QUİT
Turkish has a dotted and a dotless i, so the uppercase of i is İ, not I. ToUpper() follows the current culture, so the comparison fails.
Fix: compare without changing case at all.
if (command.Equals("quit",
StringComparison.OrdinalIgnoreCase))
Console.WriteLine("Bye!");
Bye!
2. double.Parse on a config value
string input = "3.14"; // from a config file
double price = double.Parse(input);
Console.WriteLine($"Price: {price}");
en-US: Price: 3.14
de-DE: Price: 314
In German, . is the thousands separator, so 3.14 reads as three hundred fourteen. No exception, just a price a hundred times too high.
Fix: text written by a machine should be parsed with a fixed culture.
double price = double.Parse(input,
CultureInfo.InvariantCulture);
de-DE: Price: 3,14
en-US: Price: 3.14
The value is now right everywhere. Displaying it as 3,14 to a German user is correct: localize what people read, never what machines read.
3. DateTime.Parse on 03/04/2026
string shipped = "03/04/2026"; // written by the US office
var date = DateTime.Parse(shipped);
Console.WriteLine($"Shipped: {date:MMMM d}");
en-US: Shipped: March 4
en-GB: Shipped: April 3
Same string, two dates a month apart, and both parse successfully.
Fix: store dates as ISO 8601 and parse them with an exact format.
string shipped = "2026-03-04"; // ISO 8601: no guessing
var date = DateTime.ParseExact(shipped, "yyyy-MM-dd",
CultureInfo.InvariantCulture);
en-GB: Shipped: March 4
4. Dictionary keys and capital letters
var users = new Dictionary<string, string>
{
["ada@x.dev"] = "Ada",
};
string login = "Ada@X.dev"; // what the user typed
Console.WriteLine(users.TryGetValue(login, out var name)
? $"Welcome, {name}"
: "User not found");
User not found
A Dictionary<string, …> compares keys exactly, so case matters.
Fix: give the dictionary a comparer.
var users = new Dictionary<string, string>(
StringComparer.OrdinalIgnoreCase)
{
["ada@x.dev"] = "Ada",
};
Welcome, Ada
Why not just call ToLower() on every key? Because of trap 1 again. On a Turkish machine:
"QUIT".ToLower() = quıt
5. Two strings that look identical
string typed = "café"; // typed on a keyboard
string imported = "café"; // e + combining accent
Console.WriteLine(typed);
Console.WriteLine(imported);
Console.WriteLine(typed == imported);
café
café
False
The first é is one character. The second is a plain e followed by a combining accent: lengths 4 and 5. Text pasted from a Mac, a PDF or another system often arrives in the second form.
Fix: normalize before comparing or storing.
bool same = typed.Normalize() == imported.Normalize();
True
(A culture-aware string.Equals(a, b, StringComparison.CurrentCulture) already treats them as equal. But ==, dictionary keys, hash sets and most database indexes compare ordinally, so normalize once on the way in.)
Recap
- Compare with
StringComparison.OrdinalIgnoreCase, notToUpper()orToLower(). - Parse machine-written numbers with
CultureInfo.InvariantCulture. - Store dates as ISO 8601 and use
ParseExact. - Give string-keyed dictionaries a comparer.
-
Normalize()text that comes from somewhere else.
Has a culture bug ever bitten you in production? Tell me which one in the comments.
I make short, tested videos about C# bugs that compile fine and still break things. More on the Naze Code YouTube channel.
Sources: Microsoft Learn, Best practices for comparing strings, CultureInfo.InvariantCulture, String.Normalize. Tested on .NET SDK 10.0.302 on Windows (ICU globalization), with the culture set per run.
Top comments (0)