Simple class to simpify runtime file loading of ressources. The class reads all files from a given directory including all subdirectories and stores the name together with the path in a simple struct. If a file needs to be used by the program just pass the name to GetPath() and the class returns the location of the file.
Any suggestions/improvements about my implementation of this? Im especially not sure if making this static is the right choice.
using System.IO;
using System.Collections.Generic;
namespace Sample
{
struct File
{
public string Name;
public string Path;
}
static class FileManager
{
private static List<File> Files = new List<File>();
public static void AddFiles(string directory)
{
foreach(string file in Directory.GetFiles(directory))
{
Files.Add(new File() { Name = Path.GetFileName(file), Path = directory });
}
foreach(string subdirectory in Directory.GetDirectories(directory))
{
AddFiles(subdirectory);
}
}
public static string GetPath(string filename)
{
var File = Files.Find(x => x.Name == filename);
return File.Path;
}
public static void ClearFiles()
{
Files.Clear();
}
}
}
1 Answer 1
I don't think I would make this class static, unless it's going to be used extensively throughout the program. If not you can build up a large list of files, that may hang around in memory for no use, unless you remember to call ClearFiles()
. Instead you could make a static method that could return an initialized object like:
public static FileManager Create(string directoryPath)
{
FileManager fm = new FileManager();
fm.AddFiles(directoryPath);
return fm;
}
If you have a need for it, then make this instance as static somewhere in the application.
public static string GetPath(string filename) { var File = Files.Find(x => x.Name == filename); return File.Path; }
It returns only a first match of possible more matches, which will be in a directory high in the hierarchy, but what if you actually seek a path to a file in a subdirectory?
I think I would return a list/array/IEnumerable instead and let the client filter as needed.
Besides that, file names are case insensitive, so you should do:
Files.Find(x => string.Equals(x.Name, filename, StringComparison.CurrentCultureIgnoreCase));
public static void AddFiles(string directory) { foreach (string file in Directory.GetFiles(directory)) { Files.Add(new File() { Name = Path.GetFileName(file), Path = directory }); } foreach (string subdirectory in Directory.GetDirectories(directory)) { AddFiles(subdirectory); } }
Nice recursive method. As an alternative you could consider to use DirectoryInfo
instead - it can handle the recursive search for you:
DirectoryInfo directory = new DirectoryInfo(directoryPath);
Files.AddRange(
directory
.GetFiles("*.*", SearchOption.AllDirectories)
.Select(fi => new File { Name = fi.Name, Path = fi.DirectoryName }));
There is no way to iterate through all the found File objects because the Files
static member is private. I would consider to provide a public IEnumerable of some kind.
All in all, my implementation would look something like:
public struct File
{
public string Name;
public string Path;
public override string ToString()
{
return $"{Name} => {Path}";
}
}
public class FileManager : IEnumerable<File>
{
private List<File> Files = new List<File>();
public void AddFiles(string directoryPath)
{
DirectoryInfo directory = new DirectoryInfo(directoryPath);
Files.AddRange(
directory
.GetFiles("*.*", SearchOption.AllDirectories)
.Select(fi => new File { Name = fi.Name, Path = fi.DirectoryName }));
}
public IEnumerable<string> GetPaths(string filename)
{
return Files
.Where(x => string.Equals(x.Name, filename, StringComparison.CurrentCultureIgnoreCase))
.Select(f => f.Path);
}
public void Clear()
{
Files.Clear();
}
public IEnumerator<File> GetEnumerator()
{
return Files.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public static FileManager Create(string directoryPath)
{
FileManager fm = new FileManager();
fm.AddFiles(directoryPath);
return fm;
}
}
-
\$\begingroup\$ Windows 10 supports marking directories/entire file systems as case-insensitive. It's not mentioned, but if this were dotnet core, then running on Linux/Mac would also support that. That said, if you're going to ignore case, I believe the proper thing to do for windows files (assuming case insensitivity) is to use
OrdinalIgnoreCase
\$\endgroup\$pinkfloydx33– pinkfloydx332019年04月13日 11:56:41 +00:00Commented Apr 13, 2019 at 11:56 -
\$\begingroup\$ Too late to edit my comment, but that first line was supposed to read: Windows10 supports... case sensitive \$\endgroup\$pinkfloydx33– pinkfloydx332019年04月13日 13:20:44 +00:00Commented Apr 13, 2019 at 13:20
Find
and how does it handle files with same names? Please add its implementation to the question. Oh, or not... I see it's aList
's method haha, I've never used it before ;-] and it returns the first match. Interesting. Is this what you want? Why don't you useFirstOrDefault
which I find much cleaner because you clearly see what you'll get. \$\endgroup\$