Path.GetDirectoryName() は overload によって挙動が異なる
久々に Path.GetDirectoryName() を使ったら、件名の通りの挙動に引っ掛かりました。文章で説明するよりもサンプルコードを見た方が早いので、以下をご覧ください。
const string fileName = "A/B/C/D.cs"; var dir1 = Path.GetDirectoryName(fileName); dir1.Dump("string"); // A\B\C var dir2 = Path.GetDirectoryName(fileName.AsSpan()); dir2.Dump("ReadOnlySpan<char>"); // A/B/C

ご覧の通り string の overload だと / が \ に化けますが、ReadOnlySpan<char> の方はそうなりません。だいぶ罠ですね。
string の overload は .SubString() をして返すだろうことは容易に想像が付きますが、\ への変換までオマケで付いてきます。PathInternal.NormalizeDirectorySeparators() なんて余計なことを...。 ReadOnlySpan<char> の overload は .Slice() をするだけでメモリアロケーションもなく、加えて変なオマケも付いてこないので安心です。

string の overload は過去の遺産ということで見なかったことにして、ReadOnlySpan<char> の overload を使っていきましょう。