tags:

views:

62

answers:

3

I was hit in the face by a very weird behavior of the System.IO.Directory.GetParent method:

string path1 = @"C:\foo\bar";
DirectoryInfo parent1 = Directory.GetParent(path1);
Console.WriteLine (parent1.FullName); // Prints C:\foo, as expected

// Notice the extra backslash. It should still refer to the same location, right ?
string path2 = @"C:\foo\bar\";
DirectoryInfo parent2 = Directory.GetParent(path2);
Console.WriteLine (parent2.FullName); // Prints C:\foo\bar !!!

I would consider it a bug, but this method has been there since 1.0, so I guess it would have been detected by now. On the other hand, if it's as designed, I can't think of a sensible explanation for such a design...

What do you think ? Is it a bug ? If not, how do you explain this behavior ?

+2  A: 

Some googling reveals some thoughts:

DirectoryInfo di = new DirectoryInfo(@"C:\parent\child");
Console.WriteLine(di.Parent.FullName);

and

DirectoryInfo di = new DirectoryInfo(@"C:\parent\child\");
Console.WriteLine(di.Parent.FullName);

Both return "C:\parent"

I can only assume Directory.GetParent(...) can't assume that C:\parent\child is a directory instead of a file with no file extension. DirectoryInfo can, because you're constructing the object that way.


Personally what I think is that when there is a backslash, the string is treated as a path to the "null file" inside the directory (that is, a file with no name and extension). Apparently, those can exist (there should be a link, but for some reason I can't find anything).

Try constructing a FileInfo object out of path2. You'll see it's properly constructed, has String.Empty as its name and extension, does not exist and has C:\foo\bar as its DirectoryName. Given that, the situation makes sense: the parent object for this "null file" is indeed C:\foo\bar.

GSerg
Yes, I thought of something like that, but it doesn't really make sense: the case without a final backslash works fine (the parent is be `C:\parent` whether `child` is a file or directory), it's the case *with* the final backslash that is failing: `c:\parent\child\` is obviously a directory, not a file (no file path ends with a backslash). And even if it *could* be a file, its parent should still be `c:\parent`...
Thomas Levesque
Regarding the workaround with `new DirectoryInfo`: that's what I did eventually. Actually my question wasn't really about "how to solve that issue", but rather about "why does it behave like that"
Thomas Levesque
@Thomas: Maybe even not.. see the edit.
GSerg
+1 for your last edit. Now it makes sense, I guess... even though I wouldn't have designed it that way
Thomas Levesque
A: 

This is pretty interesting. First when I read this I was pretty sure this would be a bug, but when I thought a little bit longer about it I came to the conclusion that probably the intent is that path should not be a directory but a full or relative path to a file. So

c:\somenonexistingpath\to\a\directory\

gets interpreted as a path to a file with no name in ...\directory. That's kind of silly, but if we assume that the programmers at Microsoft were expecting a full path to a file, it makes sense not to cover this case.

EDIT:

Note that

c:\dir\makefile -> c:\dir

c:\dir\build.msbuild -> c:\dir

give the parent as expected.

steinar
That would make sense, but the documentation doesn't say anything about the path being a file or directory path... I assume they would have mentioned it if the method expected a file path. And a file with no name wouldn't make a lot of sense ;). Anyway, I think I'm going to file a bug report on Connect, just to see what MS has to say about that.
Thomas Levesque
A file with no name doesn't make sense, but my argument is that they had no expectations that people would be assuming a path to a directory. I agree that the documentation should be clearer.
steinar
+1  A: 

I agree with GSerg. Just to add some additional firepower, I'm going to add the following code snippets obtained with Reflector.

The Directory.GetParent function basically just calls the Path.GetDirectoryName function:

[SecuritySafeCritical]
public static DirectoryInfo GetParent(string path)
{
    if (path == null)
    {
        throw new ArgumentNullException("path");
    }
    if (path.Length == 0)
    {
        throw new ArgumentException(Environment.GetResourceString("Argument_PathEmpty"), "path");
    }
    string directoryName = Path.GetDirectoryName(Path.GetFullPathInternal(path));
    if (directoryName == null)
    {
        return null;
    }
    return new DirectoryInfo(directoryName);
}

The Parent property of the DirectoryInfo basically strips off a trailing slash and then calls Path.GetDirectoryName:

public DirectoryInfo Parent
{
    [SecuritySafeCritical]
    get
    {
        string fullPath = base.FullPath;
        if ((fullPath.Length > 3) && fullPath.EndsWith(Path.DirectorySeparatorChar))
        {
            fullPath = base.FullPath.Substring(0, base.FullPath.Length - 1);
        }
        string directoryName = Path.GetDirectoryName(fullPath);
        if (directoryName == null)
        {
            return null;
        }
        DirectoryInfo info = new DirectoryInfo(directoryName, false);
        new FileIOPermission(FileIOPermissionAccess.PathDiscovery | FileIOPermissionAccess.Read, info.demandDir, false, false).Demand();
        return info;
    }
}
Russell McClure
Interesting... I didn't even think of checking with Reflector (very unlike me...). So the problem is actually with Path.GetDirectoryName, not Directory.GetParent
Thomas Levesque