Internally, there is a concept of NUL-termination of C# strings.
This is an implementation detail which is there only to provide efficient marshalling of strings when using P/Invoke or the like. Since many C/C++ libraries expect their strings to be NUL-terminated, by NUL-terminating ALL strings in memory, the CLR allows strings to be passed to a C/C++ function by simply pinning them - they don't need to be copied to a new temporary buffer with an extra NUL at the end.
However, in normal use you would NEVER need to be aware of this - the .Length property of a string always provides the correct string length.
Note that the NUL-termination of strings is also observable when you use unsafe code to access a string, like so:
unsafe
{
string tenChars = "1234567890";
int count = 0;
fixed (char* pStr = tenChars)
{
char* p = pStr;
while (*p++ != '\0')
++count;
}
Console.WriteLine(count); // Prints 10
}
Note that this does NOT throw an exception - it allows you to access the value one beyond the length of the string. Also note that using fixed to obtain a pointer to a string does NOT make a copy of the string and add a NUL-terminator to it. It points directly to the unchanged string in memory.
Having said all that, the ONLY time you need to worry about or be aware of this implementation detail is when using P/Invoke or using very low-level unsafe code.
The Microsoft documentation is a bit opaque about this.
From https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/strings/
There's no null-terminating character at the end of a C# string;
therefore a C# string can contain any number of embedded null
characters ('\0'). The Length property of a string represents the
number of Char objects it contains, not the number of Unicode
characters.
That's not strictly true, as the unsafe code above proves.
From https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/unsafe-code
A char* value produced by fixing a string instance always points to a
null-terminated string. Within a fixed statement that obtains a
pointer p to a string instance s, the pointer values ranging from p to
p + s.Length ‑ 1 represent addresses of the characters in the string,
and the pointer value p + s.Length always points to a null character
(the character with value ‘\0’).