I just came across what seems like a weird design choice:
System.IO.Path.GetDirectoryName(@\"C:\\folder\\file.ext\")
returns \"C:\\folder\"
Actually the function would have been better named GetPathName. it returns the full path of the directory of the filename you passed.
OK, I know this question hasn't been touched in years, but no one has really answered the question of 'Why is this the case?', so I'm going to take a long winded crack at it.
For the purposes of this explanation only, a path is a conceptual thing for Windows. For practical purposes there are only two types of paths.
I'm deliberately leaving out relative paths because all relative paths can be converted into a Full Path that is one of the above two.
Consider the following code:
var examplePaths = new[]
{
"C:\\Folder\\File.ext",
"C:\\Folder\\",
"C:\\Folder",
"C:\\",
"C:",
"\\\\Server\\Share\\Folder\\File.ext",
"\\\\Server\\Share\\Folder\\",
"\\\\Server\\Share\\Folder",
"\\\\Server\\Share\\",
"\\\\Server\\Share",
"\\\\Server\\",
"\\\\Server",
"\\\\",
};
foreach(var path in examplePaths)
{
var result = Path.GetDirectoryName(path) ?? "NULL";
Console.WriteLine($"'{path}'\t'{result}'");
}
If you run this, there is what I think, an interesting pattern:
C:\Folder\File.ext C:\Folder
C:\Folder\ C:\Folder
C:\Folder C:\
C:\ NULL
C: NULL
\\Server\Share\Folder\File.ext \\Server\Share\Folder
\\Server\Share\Folder\ \\Server\Share\Folder
\\Server\Share\Folder \\Server\Share
\\Server\Share\ \\Server\Share
\\Server\Share NULL
\\Server\ NULL
\\Server NULL
\\ NULL
Lets talk about what a path is conceptually. First All the way to the left, is the path root. C:\ is a root. \Server\Share is a root. (Have a look at GetRootLength source code to see how it determines what part of the path is the root.)
Looking at the above table, we see that everything that returned a null was either a root path all by itself or something less than a root.
We can map things out conceptually:
C:\Folder\File.ext Root\Directory\Filename
C:\Folder\ Root\Directory\
C:\Folder Root\Directory
C:\ Root\
C: Root
\\Server\Share\Folder\File.ext Root\Directory\Filename
\\Server\Share\Folder\ Root\Directory\
\\Server\Share\Folder Root\Directory
\\Server\Share\ Root\
\\Server\Share Root
\\Server\ Root
\\Server Root
\\ Root
What I am getting at is that C: and \Server\Share are both roots. And the '\' characters in \Server\Share have special meaning to that root. You can't naively just return whatever is left of the last '\' character.
If you look at the GetDirectoryName source, you can can see that they take this into account, by requiring the variable i be greater than the root length.
while (i > root && path[--i] != DirectorySeparatorChar && path[i] != AltDirectorySeparatorChar);
So if you pass this function a path like 'C:\' the path parameter is a root all on its own.
Now, maybe its not the best function name, but what GetDirectoryName might reasonably be thought of as 'Get the the directory that contains this file (or directory)' and when you think about it that way it suddenly make sense. Maybe GetParentDirectory would have been a better name.
Get the directory that contains C:\Folder\file.ext, The obvious result is C:\Folder.
Get the directory that contains C:\Folder, The result is the Root Directory on C:\ contains Folder.
Get the directory that contains C:\ and the the answer has to be NULL/nothing because the root directory has no parent.
From the function's documentation:
Return Value Type:
A String containing directory information for path, or null reference (Nothing in Visual Basic) if path denotes a root directory(emphasis added by me)