更好的方式检查路径是文件还是目录?

490

我正在处理一个目录和文件的 TreeView。用户可以选择文件或目录,然后对其执行某些操作。这需要我编写一个方法,根据用户的选择执行不同的操作。

目前我正在做类似于下面的事情来确定路径是文件还是目录:

bool bIsFile = false;
bool bIsDirectory = false;

try
{
    string[] subfolders = Directory.GetDirectories(strFilePath);

    bIsDirectory = true;
    bIsFile = false;
}
catch(System.IO.IOException)
{
    bIsFolder = false;
    bIsFile = true;
}

我感觉一定有更好的方法!我希望能够找到标准的 .NET 方法来处理这个问题,但我没有找到。是否存在这样的方法?如果不存在,最直接的方法是确定一个路径是文件还是目录?


11
可以有人编辑问题标题,以明确指定“现有”的文件/目录吗?所有答案都适用于磁盘上存在的文件/目录的路径。 - Jake Berger
1
@jberger请参考我下面的答案。我找到了一种方法来处理可能存在或不存在的文件/文件夹路径。 - lhan
你是如何填充这个树形视图的?你是如何获取其中的路径的? - Random832
23个回答

721

来自如何判断路径是文件还是目录

// get the file attributes for file or directory
FileAttributes attr = File.GetAttributes(@"c:\Temp");

//detect whether its a directory or file
if ((attr & FileAttributes.Directory) == FileAttributes.Directory)
    MessageBox.Show("Its a directory");
else
    MessageBox.Show("Its a file");

针对 .NET 4.0+ 版本的更新

根据下面的评论,如果你使用的是 .NET 4.0 或更高版本(且最大性能不是关键问题),你可以以更整洁的方式编写代码:

// get the file attributes for file or directory
FileAttributes attr = File.GetAttributes(@"c:\Temp");

if (attr.HasFlag(FileAttributes.Directory))
    MessageBox.Show("Its a directory");
else
    MessageBox.Show("Its a file");

9
这是更好的方法,比我提出的解决方案快得多。+1 - Andrew Hare
7
@KeyMs92 这是位运算。基本上,attr 是一个二进制值,其中一位表示“这是一个目录”。按位与&运算符将返回一个二进制值,其中仅打开了两个操作数中都打开(1)的位。在这种情况下,对 attrFileAttributes.Directory 值执行按位与操作将返回 FileAttributes.Directory 的值,如果目录文件属性位被打开的话。请参阅 http://en.wikipedia.org/wiki/Bitwise_operation 以获取更好的解释。 - Kyle Trauberman
28
在 .NET 4.0 之后,可以使用 attr.HasFlag(FileAttributes.Directory) 替代。 - Şafak Gür
16
不要在时间敏感的循环内执行此操作。attr.HasFlag()非常慢,每次调用都会使用反射。 - springy76
6
试一试!(请先粘贴到编辑器中并重新格式化 ;)) /// 如果路径是目录则返回 true,如果是文件则返回 false,如果既不是也不存在则返回 null。 public static bool? IsDirFile(this string path) { bool? result = null; if(Directory.Exists(path) || File.Exists(path)) { // 获取文件或目录的文件属性 var fileAttr = File.GetAttributes(path); if (fileAttr.HasFlag(FileAttributes.Directory)) result = true; else result = false; } return result; } - Michael Socha
显示剩余18条评论

324

试试这个怎么样?

if(File.Exists(data.path))
{
    // is file
}
else if(Directory.Exists(data.path))
{
   // is Folder 
}
else
{
   // invalid path
}

即使目录存在,如果它不是文件,File.Exists()将返回false,所以如果其返回true,则说明我们得到了一个文件;如果返回false,则可能是一个目录或无效路径,因此我们接下来使用Directory.Exists()测试其是否为有效目录,如果返回true,则说明我们有一个目录,否则就是无效路径。


60
File.GetAttributes() 不同,这还有一个好处,就是在路径无效时不会抛出异常。 - Deanna
4
@jberger我认为它不适用于指向不存在文件/文件夹的路径。例如File.Exists(“c:\ temp \ nonexistant.txt”)应该返回false,就像它现在做的一样。 - michaelkoss
15
如果你担心不存在的文件/文件夹,请尝试这样做:public static bool? IsDirectory(string path){ if (Directory.Exists(path)) return true; // 是一个文件夹 else if (File.Exists(path)) return false; // 是一个文件 else return null; // 不存在 } - Dustin Townsend
1
更多关于此的详细信息请参见http://msdn.microsoft.com/en-us/library/system.io.directory.exists(v=vs.110).aspx。 - Moji
+1 是因为我喜欢这个解决方案,但是在删除文件或目录时要考虑一下。那时你就没有路径了。 - Mitulát báti
显示剩余4条评论

25

只需这一行代码,就可以确定路径是目录还是文件:

File.GetAttributes(data.Path).HasFlag(FileAttributes.Directory)

5
请注意,您需要至少 .NET 4.0 才能运行此程序。另外,如果路径不是有效路径,程序将会崩溃。 - nawfal
使用FileInfo对象检查路径是否存在:FileInfo pFinfo = new FileInfo(FList [0]); 如果(pFinfo.Exists){if (File.GetAttributes(FList [0]).HasFlag(FileAttributes.Directory)) {} }。这个对我有效。 - Michael Stimson
1
如果您已经创建了一个FileInfo对象并且正在使用实例的Exists属性,为什么不访问其Attributes属性而不是使用静态的File.GetAttributes()方法呢? - dynamichael

15

这是我的:

    bool IsPathDirectory(string path)
    {
        if (path == null) throw new ArgumentNullException("path");
        path = path.Trim();

        if (Directory.Exists(path)) 
            return true;

        if (File.Exists(path)) 
            return false;

        // neither file nor directory exists. guess intention

        // if has trailing slash then it's a directory
        if (new[] {"\\", "/"}.Any(x => path.EndsWith(x)))
            return true; // ends with slash

        // if has extension then its a file; directory otherwise
        return string.IsNullOrWhiteSpace(Path.GetExtension(path));
    }

这与其他人的答案相似,但并不完全相同。


4
技术上应该使用 Path.DirectorySeparatorCharPath.AltDirectorySeparatorChar - drzaus
2
这个猜测意图的想法很有趣。在我看来,最好将其拆分为两种方法。第一种方法执行存在性测试,返回可空布尔值。如果调用者希望获得“猜测”部分,在第一种方法返回 null 的情况下,则调用第二种方法进行猜测。 - ToolmakerSteve
2
我会重写这个函数,返回一个元组,其中包含它是否猜测成功的信息。 - Ronnie Overby
4
“如果有扩展名则是文件” - 这并不正确。即使在Windows中,文件也不一定需要有扩展名,而一个目录也可以有“扩展名”。例如,这既可以是文件,也可以是目录:“C:\New folder.log”。 - bytedev
2
@bytedev 我知道,但在函数的那一点上,代码正在猜测意图。甚至有一个注释说明了这一点。大多数文件都有扩展名,而大多数目录则没有。 - Ronnie Overby
检查文件扩展名并不安全,因为文件可能具有空扩展名。如果使用有效路径,则不应仅使用最终检查,但是如果该方法用于随机字符串(可能表示有效的文件名,但与磁盘上的实际文件不对应),则该检查可能会有用。 - AlainD

8

在结合其他回答的建议后,我意识到我想到的与Ronnie Overby's answer大致相同。以下是一些测试,以指出需要考虑的一些事情:

  1. 文件夹可以有“扩展名”:C:\Temp\folder_with.dot
  2. 文件不能以目录分隔符(斜杠)结尾
  3. 从技术上讲,有两个特定于平台的目录分隔符——即可能是斜杠也可能不是(Path.DirectorySeparatorCharPath.AltDirectorySeparatorChar

测试(Linqpad)

var paths = new[] {
    // exists
    @"C:\Temp\dir_test\folder_is_a_dir",
    @"C:\Temp\dir_test\is_a_dir_trailing_slash\",
    @"C:\Temp\dir_test\existing_folder_with.ext",
    @"C:\Temp\dir_test\file_thats_not_a_dir",
    @"C:\Temp\dir_test\notadir.txt",
    // doesn't exist
    @"C:\Temp\dir_test\dne_folder_is_a_dir",
    @"C:\Temp\dir_test\dne_folder_trailing_slash\",
    @"C:\Temp\dir_test\non_existing_folder_with.ext",
    @"C:\Temp\dir_test\dne_file_thats_not_a_dir",
    @"C:\Temp\dir_test\dne_notadir.txt",        
};

foreach(var path in paths) {
    IsFolder(path/*, false*/).Dump(path);
}

结果

C:\Temp\dir_test\folder_is_a_dir
  True 
C:\Temp\dir_test\is_a_dir_trailing_slash\
  True 
C:\Temp\dir_test\existing_folder_with.ext
  True 
C:\Temp\dir_test\file_thats_not_a_dir
  False 
C:\Temp\dir_test\notadir.txt
  False 
C:\Temp\dir_test\dne_folder_is_a_dir
  True 
C:\Temp\dir_test\dne_folder_trailing_slash\
  True 
C:\Temp\dir_test\non_existing_folder_with.ext
  False (this is the weird one)
C:\Temp\dir_test\dne_file_thats_not_a_dir
  True 
C:\Temp\dir_test\dne_notadir.txt
  False 

方法

/// <summary>
/// Whether the <paramref name="path"/> is a folder (existing or not); 
/// optionally assume that if it doesn't "look like" a file then it's a directory.
/// </summary>
/// <param name="path">Path to check</param>
/// <param name="assumeDneLookAlike">If the <paramref name="path"/> doesn't exist, does it at least look like a directory name?  As in, it doesn't look like a file.</param>
/// <returns><c>True</c> if a folder/directory, <c>false</c> if not.</returns>
public static bool IsFolder(string path, bool assumeDneLookAlike = true)
{
    // https://dev59.com/wHM_5IYBdhLWcg3wZSPX
    // turns out to be about the same as https://dev59.com/wHM_5IYBdhLWcg3wZSPX#19596821

    // check in order of verisimilitude

    // exists or ends with a directory separator -- files cannot end with directory separator, right?
    if (Directory.Exists(path)
        // use system values rather than assume slashes
        || path.EndsWith("" + Path.DirectorySeparatorChar)
        || path.EndsWith("" + Path.AltDirectorySeparatorChar))
        return true;

    // if we know for sure that it's an actual file...
    if (File.Exists(path))
        return false;

    // if it has an extension it should be a file, so vice versa
    // although technically directories can have extensions...
    if (!Path.HasExtension(path) && assumeDneLookAlike)
        return true;

    // only works for existing files, kinda redundant with `.Exists` above
    //if( File.GetAttributes(path).HasFlag(FileAttributes.Directory) ) ...; 

    // no idea -- could return an 'indeterminate' value (nullable bool)
    // or assume that if we don't know then it's not a folder
    return false;
}

1
使用Path.DirectorySeparatorChar.ToString()代替字符串连接符"" - iCollect.it Ltd
1
@GoneCoding 可能吧;当时我一直在处理一堆可空属性,所以我养成了“与空字符串连接”的习惯,而不是担心检查 null。如果你想要真正优化,也可以使用 new String(Path.DirectorySeparatorChar, 1),因为这就是 ToString 所做的。 - drzaus

8
作为Directory.Exists()的替代方案,您可以使用File.GetAttributes()方法获取文件或目录的属性,因此您可以创建一个类似于以下内容的辅助方法:
private static bool IsDirectory(string path)
{
    System.IO.FileAttributes fa = System.IO.File.GetAttributes(path);
    return (fa & FileAttributes.Directory) != 0;
}

当填充TreeView控件时,您可以考虑向其标签属性添加对象,其中包含有关该项的其他元数据。例如,您可以为文件添加FileInfo对象,为目录添加DirectoryInfo对象,然后在标记属性中测试项目类型,以避免在单击项目时进行额外的系统调用来获取该数据。


2
这与其他答案有何不同? - Jake Berger
7
与其使用那个可怕的逻辑块,不如尝试 isDirectory = (fa & FileAttributes.Directory) != 0); - Immortal Blue

5

考虑到Exists和Attributes属性的行为,以下是我能想到的最好方法:

using System.IO;

public static class FileSystemInfoExtensions
{
    /// <summary>
    /// Checks whether a FileInfo or DirectoryInfo object is a directory, or intended to be a directory.
    /// </summary>
    /// <param name="fileSystemInfo"></param>
    /// <returns></returns>
    public static bool IsDirectory(this FileSystemInfo fileSystemInfo)
    {
        if (fileSystemInfo == null)
        {
            return false;
        }

        if ((int)fileSystemInfo.Attributes != -1)
        {
            // if attributes are initialized check the directory flag
            return fileSystemInfo.Attributes.HasFlag(FileAttributes.Directory);
        }

        // If we get here the file probably doesn't exist yet.  The best we can do is 
        // try to judge intent.  Because directories can have extensions and files
        // can lack them, we can't rely on filename.
        // 
        // We can reasonably assume that if the path doesn't exist yet and 
        // FileSystemInfo is a DirectoryInfo, a directory is intended.  FileInfo can 
        // make a directory, but it would be a bizarre code path.

        return fileSystemInfo is DirectoryInfo;
    }
}

以下是测试结果:

    [TestMethod]
    public void IsDirectoryTest()
    {
        // non-existing file, FileAttributes not conclusive, rely on type of FileSystemInfo
        const string nonExistentFile = @"C:\TotallyFakeFile.exe";

        var nonExistentFileDirectoryInfo = new DirectoryInfo(nonExistentFile);
        Assert.IsTrue(nonExistentFileDirectoryInfo.IsDirectory());

        var nonExistentFileFileInfo = new FileInfo(nonExistentFile);
        Assert.IsFalse(nonExistentFileFileInfo.IsDirectory());

        // non-existing directory, FileAttributes not conclusive, rely on type of FileSystemInfo
        const string nonExistentDirectory = @"C:\FakeDirectory";

        var nonExistentDirectoryInfo = new DirectoryInfo(nonExistentDirectory);
        Assert.IsTrue(nonExistentDirectoryInfo.IsDirectory());

        var nonExistentFileInfo = new FileInfo(nonExistentDirectory);
        Assert.IsFalse(nonExistentFileInfo.IsDirectory());

        // Existing, rely on FileAttributes
        const string existingDirectory = @"C:\Windows";

        var existingDirectoryInfo = new DirectoryInfo(existingDirectory);
        Assert.IsTrue(existingDirectoryInfo.IsDirectory());

        var existingDirectoryFileInfo = new FileInfo(existingDirectory);
        Assert.IsTrue(existingDirectoryFileInfo.IsDirectory());

        // Existing, rely on FileAttributes
        const string existingFile = @"C:\Windows\notepad.exe";

        var existingFileDirectoryInfo = new DirectoryInfo(existingFile);
        Assert.IsFalse(existingFileDirectoryInfo.IsDirectory());

        var existingFileFileInfo = new FileInfo(existingFile);
        Assert.IsFalse(existingFileFileInfo.IsDirectory());
    }

4
public bool IsDirectory(string path) {
    return string.IsNullOrEmpty(Path.GetFileName(path)) || Directory.Exists(path);
}

检查路径文件名是否为空字符串,或者目录是否存在。这样做可以避免在提供可能的存在失败冗余的同时,出现文件属性错误。


3
最准确的方法是使用来自shlwapi.dll的一些交互代码。
[DllImport(SHLWAPI, CharSet = CharSet.Unicode)]
[return: MarshalAsAttribute(UnmanagedType.Bool)]
[ResourceExposure(ResourceScope.None)]
internal static extern bool PathIsDirectory([MarshalAsAttribute(UnmanagedType.LPWStr), In] string pszPath);

您可以这样调用它:
#region IsDirectory
/// <summary>
/// Verifies that a path is a valid directory.
/// </summary>
/// <param name="path">The path to verify.</param>
/// <returns><see langword="true"/> if the path is a valid directory; 
/// otherwise, <see langword="false"/>.</returns>
/// <exception cref="T:System.ArgumentNullException">
/// <para><paramref name="path"/> is <see langword="null"/>.</para>
/// </exception>
/// <exception cref="T:System.ArgumentException">
/// <para><paramref name="path"/> is <see cref="F:System.String.Empty">String.Empty</see>.</para>
/// </exception>
public static bool IsDirectory(string path)
{
    return PathIsDirectory(path);
}

37
丑陋。我讨厌使用交互操作来完成这些简单的任务。而且它不具备可移植性。还有,它很丑陋。我是说,我已经说过它很丑陋了吗? :) - Ignacio Soler Garcia
6
在我看来可能会有些“丑陋”,但这仍然是最准确的方法。是的,它不是一个可移植的解决方案,但问题也并没有要求这样。 - Scott Dorman
8
“accurate”在这里的确切含义是什么?它给出了与Quinn Wilson的答案相同的结果,并且需要破坏可移植性的interop。对我来说,它和其他解决方案一样准确,但有其他解决方案没有的副作用。 - Ignacio Soler Garcia
7
有一个框架API可以做到这一点。使用Interop并不是正确的方法。 - TomXP411
5
是的,这样可以实现,但它并不是“最准确”的解决方案——和使用现有的.NET Framework一样。 相反,你需要用6行代码来替换.NET Framework中可以用一行代码解决的问题,并将自己限制在仅使用Windows,而无法使用Mono项目进行移植。 当.NET Framework提供了更优雅的解决方案时,永远不要使用Interop。 - Russ
显示剩余3条评论

3

以下是我们使用的技术:

using System;

using System.IO;

namespace crmachine.CommonClasses
{

  public static class CRMPath
  {

    public static bool IsDirectory(string path)
    {
      if (path == null)
      {
        throw new ArgumentNullException("path");
      }

      string reason;
      if (!IsValidPathString(path, out reason))
      {
        throw new ArgumentException(reason);
      }

      if (!(Directory.Exists(path) || File.Exists(path)))
      {
        throw new InvalidOperationException(string.Format("Could not find a part of the path '{0}'",path));
      }

      return (new System.IO.FileInfo(path).Attributes & FileAttributes.Directory) == FileAttributes.Directory;
    } 

    public static bool IsValidPathString(string pathStringToTest, out string reasonForError)
    {
      reasonForError = "";
      if (string.IsNullOrWhiteSpace(pathStringToTest))
      {
        reasonForError = "Path is Null or Whitespace.";
        return false;
      }
      if (pathStringToTest.Length > CRMConst.MAXPATH) // MAXPATH == 260
      {
        reasonForError = "Length of path exceeds MAXPATH.";
        return false;
      }
      if (PathContainsInvalidCharacters(pathStringToTest))
      {
        reasonForError = "Path contains invalid path characters.";
        return false;
      }
      if (pathStringToTest == ":")
      {
        reasonForError = "Path consists of only a volume designator.";
        return false;
      }
      if (pathStringToTest[0] == ':')
      {
        reasonForError = "Path begins with a volume designator.";
        return false;
      }

      if (pathStringToTest.Contains(":") && pathStringToTest.IndexOf(':') != 1)
      {
        reasonForError = "Path contains a volume designator that is not part of a drive label.";
        return false;
      }
      return true;
    }

    public static bool PathContainsInvalidCharacters(string path)
    {
      if (path == null)
      {
        throw new ArgumentNullException("path");
      }

      bool containedInvalidCharacters = false;

      for (int i = 0; i < path.Length; i++)
      {
        int n = path[i];
        if (
            (n == 0x22) || // "
            (n == 0x3c) || // <
            (n == 0x3e) || // >
            (n == 0x7c) || // |
            (n  < 0x20)    // the control characters
          )
        {
          containedInvalidCharacters = true;
        }
      }

      return containedInvalidCharacters;
    }


    public static bool FilenameContainsInvalidCharacters(string filename)
    {
      if (filename == null)
      {
        throw new ArgumentNullException("filename");
      }

      bool containedInvalidCharacters = false;

      for (int i = 0; i < filename.Length; i++)
      {
        int n = filename[i];
        if (
            (n == 0x22) || // "
            (n == 0x3c) || // <
            (n == 0x3e) || // >
            (n == 0x7c) || // |
            (n == 0x3a) || // : 
            (n == 0x2a) || // * 
            (n == 0x3f) || // ? 
            (n == 0x5c) || // \ 
            (n == 0x2f) || // /
            (n  < 0x20)    // the control characters
          )
        {
          containedInvalidCharacters = true;
        }
      }

      return containedInvalidCharacters;
    }

  }

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接