Three ways to count a string
string.Lengthcounts UTF-16 code units. Characters above U+FFFF, such as most emoji, take two (a surrogate pair), so "😀".Length is 2.EnumerateRunes()counts code points (Rune, .NET Core 3.0 and later): the emoji is 1.StringInfo.LengthInTextElementscounts what a reader sees as characters (grapheme clusters): "e" plus a combining accent, a thumbs-up with a skin tone, or a flag made of two regional letters each count as one.
Truncating with Substring can cut a surrogate pair in half and leave a lone surrogate, which encoders turn into U+FFFD (�). Cut on text elements (StringInfo) when the text is user-visible.
Normalization
"é" can be one code point (U+00E9) or two (e plus U+0301). They look identical but are different strings, so ==, dictionary keys and hashes disagree. string.Normalize() (form C by default) makes them equal; normalize text from users before comparing or storing it.
FAQ
Why is string.Length of an emoji 2 in C#?
Length counts UTF-16 code units, and characters above U+FFFF need two (a surrogate pair). Use EnumerateRunes().Count() for code points or StringInfo.LengthInTextElements for visible characters.
How do I write a Unicode character in a C# string?
"\u00e9" for characters up to U+FFFF and "\U0001F600" for the rest, or the character itself: C# source files are UTF-8.
What is a surrogate pair?
Two UTF-16 code units (D800-DBFF then DC00-DFFF) that together encode one code point above U+FFFF. Either half alone is invalid.