I am processing a TreeView of directories and files. A user can select either a file or a directory and then do something with it. This requires me to have a method which performs different actions based on the user's selection.
At the moment I am doing something like this to determine whether the path is a file or a directory:
bool bIsFile = false;
bool bIsDirectory = false;
try
{
string[] subfolders = Directory.GetDirectories(strFilePath);
bIsDirectory = true;
bIsFile = false;
}
catch(System.IO.IOException)
{
bIsFolder = false;
bIsFile = true;
}
I cannot help to feel that there is a better way to do this! I was hoping to find a standard .NET method to handle this, but I haven't been able to do so. Does such a method exist, and if not, what is the most straightforward means to determine whether a path is a file or directory?
From How to tell if path is file or directory:
// 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");
Update for .NET 4.0+
Per the comments below, if you are on .NET 4.0 or later (and maximum performance is not critical) you can write the code in a cleaner way:
// 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");
How about using this?
if(File.Exists(data.path))
{
// is file
}
else if(Directory.Exists(data.path))
{
// is Folder
}
else
{
// invalid path
}
File.Exists() will return false if it's not a file even if the directory does exist, so if it returns true, we know we got a file, if it returns false, we either have a directory or an invalid path so next we test if it's a valid directory with Directory.Exists() if that returns true, we have a directory if not it's an invalid path.
With only this line you can get if a path is a directory or a file:
File.GetAttributes(data.Path).HasFlag(FileAttributes.Directory)
Here's mine:
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));
}
It's similar to others' answers but not exactly the same.
As an alternative to Directory.Exists(), you can use the File.GetAttributes() method to get the attributes of a file or a directory, so you could create a helper method like this:
private static bool IsDirectory(string path)
{
System.IO.FileAttributes fa = System.IO.File.GetAttributes(path);
return (fa & FileAttributes.Directory) != 0;
}
You could also consider adding an object to the tag property of the TreeView control when populating the control that contains additional metadata for the item. For instance, you could add a FileInfo object for files and a DirectoryInfo object for directories and then test for the item type in the tag property to save making additional system calls to get that data when clicking on the item.
After combining the suggestions from the other answers, I realized I came up with about the same thing as Ronnie Overby's answer. Here are some tests to point out some things to think about:
folders can have "extensions": C:\Temp\folder_with.dot
files cannot end with a directory separator (slash)
There are technically two directory separators which are platform specific -- i.e. may or may not be slashes (Path.DirectorySeparatorChar and Path.AltDirectorySeparatorChar)
Tests (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);
}
Results
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
Method
/// <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://stackoverflow.com/questions/1395205/better-way-to-check-if-path-is-a-file-or-a-directory
// turns out to be about the same as https://stackoverflow.com/a/19596821/1037948
// 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;
}
This was the best I could come up with given the behavior of the Exists and Attributes properties:
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;
}
}
Here's how it tests out:
[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());
}
public bool IsDirectory(string path) {
return string.IsNullOrEmpty(Path.GetFileName(path)) || Directory.Exists(path);
}
Checks if the path file name is an empty string, or if the directory exists. This way you won't have the file attributes error while still providing redundancies for a possible exists failure.
Here's what we use:
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;
}
}
}
The most accurate approach is going to be using some interop code from the 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);
You would then call it like this:
#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);
}
I came across this when facing a similar problem, except I needed to check if a path is for a file or folder when that file or folder may not actually exist. There were a few comments on answers above that mentioned they would not work for this scenario. I found a solution (I use VB.NET, but you can convert if you need) that seems to work well for me:
Dim path As String = "myFakeFolder\ThisDoesNotExist\"
Dim bIsFolder As Boolean = (IO.Path.GetExtension(path) = "")
'returns True
Dim path As String = "myFakeFolder\ThisDoesNotExist\File.jpg"
Dim bIsFolder As Boolean = (IO.Path.GetExtension(path) = "")
'returns False
Hopefully this can be helpful to someone!
soooo late in the game i know, but thought i'd share this anyway. If you are solely working with the paths as strings, figuring this out is easy as pie:
private bool IsFolder(string ThePath)
{
string BS = Path.DirectorySeparatorChar.ToString();
return Path.GetDirectoryName(ThePath) == ThePath.TrimEnd(BS.ToCharArray());
}
for example:
ThePath == "C:\SomeFolder\File1.txt" would end up being this:
return "C:\SomeFolder" == "C:\SomeFolder\File1.txt" (FALSE)
Another example:
ThePath == "C:\SomeFolder\" would end up being this:
return "C:\SomeFolder" == "C:\SomeFolder" (TRUE)
And this would also work without the trailing backslash:
ThePath == "C:\SomeFolder" would end up being this:
return "C:\SomeFolder" == "C:\SomeFolder" (TRUE)
Keep in mind here that this only works with the paths themselves, and not the relationship between the path and the "physical disk"... so it can't tell you if the path/file exists or anything like that, but it sure can tell you if the path is a folder or a file...
If you want to find directories, including those that are marked "hidden" and "system", try this (requires .NET V4):
FileAttributes fa = File.GetAttributes(path);
if(fa.HasFlag(FileAttributes.Directory))
I needed this, the posts helped, this gets it down to one line, and if the path isn't a path at all, it just returns and exits the method. It addresses all of the above concerns, doesn't need the trailing slash either.
if (!Directory.Exists(#"C:\folderName")) return;
I see, I'm 10 years too late to the party.
I was facing the situation, where from some property I can receive either a file name or a full file path. If there is no path provided, I have to check the file-existence by attaching a "global" directory-path provided by another property.
In my case
var isFileName = System.IO.Path.GetFileName (str) == str;
did the trick.
Ok, it's not magic, but perhaps this could save someone a few minutes of figuring out.
Since this is merely a string-parsing, so Dir-names with dots may give false positives...
I use the following, it also tests the extension which means it can be used for testing if the path supplied is a file but a file that doesn't exist.
private static bool isDirectory(string path)
{
bool result = true;
System.IO.FileInfo fileTest = new System.IO.FileInfo(path);
if (fileTest.Exists == true)
{
result = false;
}
else
{
if (fileTest.Extension != "")
{
result = false;
}
}
return result;
}
using System;
using System.IO;
namespace FileOrDirectory
{
class Program
{
public static string FileOrDirectory(string path)
{
if (File.Exists(path))
return "File";
if (Directory.Exists(path))
return "Directory";
return "Path Not Exists";
}
static void Main()
{
Console.WriteLine("Enter The Path:");
string path = Console.ReadLine();
Console.WriteLine(FileOrDirectory(path));
}
}
}
Using the selected answer on this post, I looked at the comments and give credence to
#ŞafakGür, #Anthony and #Quinn Wilson for their info bits that lead me to this improved answer which I wrote and tested:
/// <summary>
/// Returns true if the path is a dir, false if it's a file and null if it's neither or doesn't exist.
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public static bool? IsDirFile(this string path)
{
bool? result = null;
if(Directory.Exists(path) || File.Exists(path))
{
// get the file attributes for file or directory
var fileAttr = File.GetAttributes(path);
if (fileAttr.HasFlag(FileAttributes.Directory))
result = true;
else
result = false;
}
return result;
}
Maybe for UWP C#
public static async Task<IStorageItem> AsIStorageItemAsync(this string iStorageItemPath)
{
if (string.IsNullOrEmpty(iStorageItemPath)) return null;
IStorageItem storageItem = null;
try
{
storageItem = await StorageFolder.GetFolderFromPathAsync(iStorageItemPath);
if (storageItem != null) return storageItem;
} catch { }
try
{
storageItem = await StorageFile.GetFileFromPathAsync(iStorageItemPath);
if (storageItem != null) return storageItem;
} catch { }
return storageItem;
}
Very late to the party here but I've found the Nullable<Boolean> return value to be quite ugly - IsDirectory(string path) returning null doesn't equate to a non-existent path without verbose commenting, so I've come up with the following:
public static class PathHelper
{
/// <summary>
/// Determines whether the given path refers to an existing file or directory on disk.
/// </summary>
/// <param name="path">The path to test.</param>
/// <param name="isDirectory">When this method returns, contains true if the path was found to be an existing directory, false in all other scenarios.</param>
/// <returns>true if the path exists; otherwise, false.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="path"/> is null.</exception>
/// <exception cref="ArgumentException">If <paramref name="path"/> equals <see cref="string.Empty"/></exception>
public static bool PathExists(string path, out bool isDirectory)
{
if (path == null) throw new ArgumentNullException(nameof(path));
if (path == string.Empty) throw new ArgumentException("Value cannot be empty.", nameof(path));
isDirectory = Directory.Exists(path);
return isDirectory || File.Exists(path);
}
}
This helper method is written to be verbose and concise enough to understand the intent the first time you read it.
/// <summary>
/// Example usage of <see cref="PathExists(string, out bool)"/>
/// </summary>
public static void Usage()
{
const string path = #"C:\dev";
if (!PathHelper.PathExists(path, out var isDirectory))
return;
if (isDirectory)
{
// Do something with your directory
}
else
{
// Do something with your file
}
}
Just adding a fringe case - "Folder Selection." in path
In my app I get recently opened paths passed to me, some of which have "Folder Selection." at the end.
Some FileOpenDialogs and WinMerge add "Folder Selection." to paths (it's true).
But under Windows OS "Folder Selection." is not an advised file or folder name (as in don't do it, ever - shakes fist).
As said here: http://msdn.microsoft.com/en-us/library/aa365247%28VS.85%29.aspx
Do not end a file or directory name with a space or a period. Although the underlying file system may support such names, the Windows shell and user interface does not. However, it is acceptable to specify a period as the first character of a name. For example, ".temp".
So whilst "Folder Selection." shouldn't be used, it can be. (awesome).
Enough explanation - my code (I like enums a lot):
public static class Utility
{
public enum ePathType
{
ePathType_Unknown = 0,
ePathType_ExistingFile = 1,
ePathType_ExistingFolder = 2,
ePathType_ExistingFolder_FolderSelectionAdded = 3,
}
public static ePathType GetPathType(string path)
{
if (File.Exists(path) == true) { return ePathType.ePathType_ExistingFile; }
if (Directory.Exists(path) == true) { return ePathType.ePathType_ExistingFolder; }
if (path.EndsWith("Folder Selection.") == true)
{
// Test the path again without "Folder Selection."
path = path.Replace("\\Folder Selection.", "");
if (Directory.Exists(path) == true)
{
// Could return ePathType_ExistingFolder, but prefer to let the caller known their path has text to remove...
return ePathType.ePathType_ExistingFolder_FolderSelectionAdded;
}
}
return ePathType.ePathType_Unknown;
}
}
This is my solution, beware that i was looking for a function that strictly avoid any unnecessary file system access at all, but only string manipulations are allowed here (paths may not exist):
public static bool IsFolder(string path)
{
if (string.IsNullOrEmpty(path)) return false;
if (path.EndsWith("\\")) return true;
return (path.Contains("\\") && string.IsNullOrEmpty(Path.GetExtension(path)));
}
Wouldn't this work?
var isFile = Regex.IsMatch(path, #"\w{1,}\.\w{1,}$");
Is there any way to search for the particular parent directory?
I know I can get the parent of the directory using this
Directory.GetParent(Directory.GetCurrentDirectory()).FullName
But this returns immediate parent, is there any way I can kind of search for particular parent in the directory hierarchy of the path?
EDIT
What I am trying to achieve is, say if I have current directory like this
C:/Project/Source/Dev/Database
So I want to reach to the directory Source
I know I can reach it by calling GetParent method twice but this I don't think is the right way to do it because what if in future my file current directory changes and it goes further down.
So I want some full proof way where I can directly find the path of the directory Source no matter how deep I am in the current directory because that is for sure that I will be inside directory Source
So something like
FindParent('Source')
You can try something like this (for loop):
private static IEnumerable<String> ParentDirectories(string directory = null) {
for (string dir = null == directory ? Directory.GetCurrentDirectory() : directory;
dir != null;
dir = Directory.GetParent(dir)?.FullName)
yield return dir;
}
Demo:
var demo = string.Join(Environment.NewLine,
ParentDirectories(#"C:/Project/Source/Dev/Database"));
Console.Write(demo);
Outcome:
C:/Project/Source/Dev/Database // initial directory
C:\Project\Source\Dev // and its all parents
C:\Project\Source
C:\Project
C:\
If you don't want to include directory itself, add .Skip(1):
var demo = string.Join(Environment.NewLine,
ParentDirectories(#"C:/Project/Source/Dev/Database").Skip(1));
Finally, if you want to find out a parent directory which ends by Source:
string dirName = "Source";
string myParent = ParentDirectories(#"C:/Project/Source/Dev/Database")
.FirstOrDefault(dir => string.Equals(
dirName,
new DirectoryInfo(dir).Name,
StringComparison.OrdinalIgnoreCase));
Console.Write(myParent);
Outcome:
C:\Project\Source
Directory.GetCurrentDirectory() returns a string that represents the full absolute path of the current directory.
If you want to get the path of a specific parent directory, you can simply use substring:
var path = Directory.GetCurrentDirectory(); // Suppose C:/Project/Source/Dev/Database
var sourceDir = new string[] {Path.DirectorySeparatorChar + "Source" + Path.DirectorySeparatorChar,
Path.AltDirectorySeparatorChar + "Source" + Path.AltDirectorySeparatorChar};
var sourcePath = path.IndexOf(sourceDir[0], StringComparison.OrdinalIgnoreCase) > -1 ?
path.Substring(0, path.IndexOf(sourceDir[0]) + sourceDir[0].Length) :
path.IndexOf(sourceDir[1], StringComparison.OrdinalIgnoreCase) > -1 ?
path.Substring(0, path.IndexOf(sourceDir[1]) + sourceDir[1].Length) :
null;
I've used Path.DirectorySeparatorChar and Path.AltDirectorySeparatorChar as separators so that the code will work the same on each platform.
Two possibles without anymore additional information about your situation:
DirectoryInfo.GetDirectories would get you all the child directories of the Current directory so you would need to switch your current to the root + 1
System.IO.Path.Combine(myPath, "..") which would act more like cd ..
Actually I thought there is an already existing way or function but if I have to write it myself then this solution I wrote worked for me
private static string GetParent(string directoryPath, string parent)
{
DirectoryInfo directory = new DirectoryInfo(directoryPath);
while (directory.Parent!=null)
{
if (directory.Parent.Name.ToLower() == parent.ToLower())
{
return directory.Parent.FullName;
}
directory = directory.Parent;
}
return null;
}
When traversing any kind of sequence (a sequence of numbers, a sequence of directories, a sequence of nodes in a graph), you have the option of employing recursion.
Directory structures (when ignoring links of any kind) are a subset of the data structure known as a Directed Acyclic Graph (or DAG) - this subset typically doesn't rejoin and fans out forever (so long as the operating system allows for the given depth of your nested directories). We typically refer to this as a Tree structure.
When put in this light, its probably no surprise to most that the concept of recursion surfaces because its quite common for programmers to apply recursion to traverse down a tree. However, whether you traverse up or down a tree is simply an implementation detail.
There is no reason you cannot use recursion to also traverse up a tree.
Here is a simple console app (written using .net6 conventions & C#10 syntax) that you can study, and then perhaps apply to solve future problems.
using System.Runtime.InteropServices; // For the [Optional] attribute
var dir = Directory.GetCurrentDirectory();
var file = Directory.GetFiles(dir).First();
var projFile = DirectoryRecursor.RecurseUpwardsUntilFileIsFoundWith(".csproj", file, 5);
Console.WriteLine(projFile);
public static class DirectoryRecursor
{
public static FileInfo? RecurseUpwardsUntilFileIsFoundWith(
string stringToMatch,
string sourceFile,
[Optional] int? maxParentDirLevel)
{
if (maxParentDirLevel is not null && maxParentDirLevel == 0) return null; // try and stave off disaster
var dirName = Path.GetDirectoryName(sourceFile);
if (dirName is null) return null;
var csprojFile = Directory.GetFiles(dirName).Where(x => x.EndsWith(stringToMatch)).SingleOrDefault();
if (csprojFile is null)
{
if (ThereIsAParentDirectory(new DirectoryInfo(sourceFile), out var parentDir))
{
return RecurseUpwardsUntilFileIsFoundWith(stringToMatch, parentDir.FullName, maxParentDirLevel - 1);
}
else
{
return null;
}
}
return new FileInfo(csprojFile);
}
public static bool ThereIsAParentDirectory(DirectoryInfo dir, out DirectoryInfo parentDir)
{
if (dir.Parent is not null)
{
parentDir = dir.Parent;
return dir.Parent.Exists;
}
parentDir = dir;
return false;
}
}
A quick side note:
An example of applied recursion both up and down a tree (which I appologize is not currently open source), is the conversation builder over at www.palavyr.com. This conversation tree is implemented as a double linked list to facilitate recursion both up and down the conversation tree. I'm planning on producing a technical blog post explaining this implementation - I'll update this response with that once I've assembled it.
I am new to C# . I have a text box where i enter the file to search and a 'search' button. on clock of search i want it to populate the files in the folder but i get the above error. Below is my code:
string[] directories = Directory.GetDirectories(#"d:\",
"*",
SearchOption.AllDirectories);
string file = textBox1.Text;
DataGrid dg = new DataGrid();
{
var files = new List<string>();
foreach (DriveInfo d in DriveInfo.GetDrives().Where(x => x.IsReady))
{
try
{
files.AddRange(Directory.GetFiles(d.RootDirectory.FullName, file , SearchOption.AllDirectories));
}
catch(Exception ex)
{
MessageBox.Show("the exception is " + ex.ToString());
//Logger.Log(e.Message); // Log it and move on
}
}
Please help me resolve it . Thanks
The most important rule when searching on a folder which potentially contains inaccessible subfolder is:
Do NOT use SearchOption.AllDirectories!
Use SearchOption.TopDirectoryOnly instead, combined with recursive search for all the accessible directories.
Using SearchOption.AllDirectories, one access violation will break your entire loop even before any file/directory is processed. But if you use SearchOption.TopDirectoryOnly, you only skip what is inaccessible.
There is more difficult way to use Directory.GetAccessControl() per child directory check to see if you have an access to a Directory before hand (this option is rather hard though - I don't really recommend this unless you know exactly how the access system works).
For recursive search, I have this code implemented for my own use:
public static List<string> GetAllAccessibleDirectories(string path, string searchPattern) {
List<string> dirPathList = new List<string>();
try {
List<string> childDirPathList = Directory.GetDirectories(path, searchPattern, SearchOption.TopDirectoryOnly).ToList(); //use TopDirectoryOnly
if (childDirPathList == null || childDirPathList.Count <= 0) //this directory has no child
return null;
foreach (string childDirPath in childDirPathList) { //foreach child directory, do recursive search
dirPathList.Add(childDirPath); //add the path
List<string> grandChildDirPath = GetAllAccessibleDirectories(childDirPath, searchPattern);
if (grandChildDirPath != null && grandChildDirPath.Count > 0) //this child directory has children and nothing has gone wrong
dirPathList.AddRange(grandChildDirPath.ToArray()); //add the grandchildren to the list
}
return dirPathList; //return the whole list found at this level
} catch {
return null; //something has gone wrong, return null
}
}
This is how you call it
List<string> accessibleDirs = GetAllAccessibleDirectories(myrootpath, "*");
Then, you only need to search/add the files among all accessible directories.
Note: this question is quite classical though. I believe there are some other better solutions out there too.
And in case there are some directories which you particularly want to avoid after you get all your accessible directories, you could also filter the List result by LINQ using part of the directory's name as keyword (i.e. Recycle.Bins).
As Ian has specified in his post, do not use recursive file listing (Directory.GetFiles(path, searchPattern, SearchOption.AllDirectories)) in case like yours, since the first exception will stop further processing.
Also, to somewhat alleviate such issues and for better results in general, you should run this program as an Administrator. This can be done by right-clicking your application in windows explorer, and then checking Run this program as an administrator option on Compatibility tab.
Also, you should use code like below to do your search, so the intermediate exceptions do not stop further searching.
static void Main(string[] args) {
string fileToFind = "*.jpg";
var files = new List<string>();
foreach (DriveInfo d in DriveInfo.GetDrives().Where(x => x.IsReady))
files.AddRange(FindDirectory(fileToFind, d.RootDirectory.FullName));
}
/// <summary>
/// This function returns the full file path of the matches it finds.
/// 1. It does not do any parameter validation
/// 2. It searches recursively
/// 3. It eats up any error that occurs when requesting files and directories within the specified path
/// 4. Supports specifying wildcards in the fileToFind parameter.
/// </summary>
/// <param name="fileToFind">Name of the file to search, without the path</param>
/// <param name="path">The path under which the file needs to be searched</param>
/// <returns>Enumeration of all valid full file paths matching the file</returns>
public static IEnumerable<string> FindDirectory(string fileToFind, string path) {
// Check if "path" directly contains "fileToFind"
string[] files = null;
try {
files = Directory.GetFiles(path, fileToFind);
} catch { }
if (files != null) {
foreach (var file in files)
yield return file;
}
// Check all sub-directories of "path" to see if they contain "fileToFInd"
string[] subDirs = null;
try {
subDirs = Directory.GetDirectories(path);
} catch { }
if (subDirs == null)
yield break;
foreach (var subDir in subDirs)
foreach (var foundFile in FindDirectory(fileToFind, subDir))
yield return foundFile;
}
I am trying to write a program to keep multiple folders in sync. To do this, I need to copy and delete files and subfolders.
To me, it doesn't make a difference if an object is a file or a folder, I want to create all necessary parent folders and copy the object, overwriting if necessary. I'm currently using a jagged array of FileSystemInfo to hold my files/folders.
This has the advantage of avoiding a duplication of code to sync files and folders separately.
However, I can't figure out how to Copy a FileSystemInfo. I'm looking for a way to be able to copy/delete/read creation or modified time that will work on both files and folders.
FileSystemInfo don't have Copy or Delete methods but is the base class for DirectoryInfo and FileInfo.
So when you loop over your FileSystemInfo objects you have to cast to the proper concrete class and use the specific copy/delete methods.
foreach( var fsi in fileSystemInfoObjects )
{
if( fsi is DirectoryInfo )
{
var directory = (DirectoryInfo)fsi;
//do something
}
else if (fsi is FileInfo )
{
var file = (FileInfo)fsi;
//do something
}
}
I used sam's answer to help me solve my problem. What I did was put the copying logic in my custom class so that I don't need to duplicate the logic whenever I use it in my code.
public class myFSInfo
{
public FileSystemInfo Dir;
public string RelativePath;
public string BaseDirectory;
public myFSInfo(FileSystemInfo dir, string basedir)
{
Dir = dir;
BaseDirectory = basedir;
RelativePath = Dir.FullName.Substring(basedir.Length + (basedir.Last() == '\\' ? 1 : 2));
}
private myFSInfo() { }
/// <summary>
/// Copies a FileInfo or DirectoryInfo object to the specified path, creating folders and overwriting if necessary.
/// </summary>
/// <param name="path"></param>
public void CopyTo(string path)
{
if (Dir is FileInfo)
{
var f = (FileInfo)Dir;
Directory.CreateDirectory(path.Substring(0,path.LastIndexOf("\\")));
f.CopyTo(path,true);
}
else if (Dir is DirectoryInfo) Directory.CreateDirectory(path);
}
}
I have a situation where I have to find a path to the first file named my.exe starting from startingdirectory & \mydir\ and go deep as needed.
Actually, IO.Directory.GetFiles is suitable but I need it stop searching after the first file is found like it is possible with FindFirstFile from WinAPI.
VB.NET
Dim findedDirectories() As String = IO.Directory.GetFiles( _
startingdirectory & "\mydir\", "my.exe", IO.SearchOption.AllDirectories)
C#
string[] findedDirectories = IO.Directory.GetFiles( _
startingdirectory + "\\mydir\\", "my.exe", IO.SearchOption.AllDirectories);
Is it possible to stop searching after the first file is found in a way that the result of the function will be a string or an empty string, not a string array? Or is here better way to search for a first file in subdirectories?
A solution like the following one could help:
/// <summary>
/// Searches for the first file matching to searchPattern in the sepcified path.
/// </summary>
/// <param name="path">The path from where to start the search.</param>
/// <param name="searchPattern">The pattern for which files to search for.</param>
/// <returns>Either the complete path including filename of the first file found
/// or string.Empty if no matching file could be found.</returns>
public static string FindFirstFile(string path, string searchPattern)
{
string[] files;
try
{
// Exception could occur due to insufficient permission.
files = Directory.GetFiles(path, searchPattern, SearchOption.TopDirectoryOnly);
}
catch (Exception)
{
return string.Empty;
}
// If matching files have been found, return the first one.
if (files.Length > 0)
{
return files[0];
}
else
{
// Otherwise find all directories.
string[] directories;
try
{
// Exception could occur due to insufficient permission.
directories = Directory.GetDirectories(path);
}
catch (Exception)
{
return string.Empty;
}
// Iterate through each directory and call the method recursivly.
foreach (string directory in directories)
{
string file = FindFirstFile(directory, searchPattern);
// If we found a file, return it (and break the recursion).
if (file != string.Empty)
{
return file;
}
}
}
// If no file was found (neither in this directory nor in the child directories)
// simply return string.Empty.
return string.Empty;
}
I guess the simplest approach would be to organise the recursion into sub-directories yourself with recursive calls to Directory.GetDirectories passing SearchOption.TopDirectoryOnly. In each directory check for the file's existence with File.Exists.
This actually mirrors the way it would be done in Win32 with FindFirstFile. When using FindFirstFile you always need to implement the sub-directory recursion yourself because FindFirstFile has nothing analagous to SearchOption.AllDirectories.