Click here to Skip to main content
Click here to Skip to main content

Class Library for recursing through directories

By , 12 Aug 2003
 

Screenshot

Introduction

This class library is primarily a utility for a larger application that might need to collate a list of files from a directory. It provides a simple means of returning a collection of files present in a directory, with the option of having the search recurse down all the directories' sub-directories. I've also implemented a way to define a collection of file extensions that filters the collection returned by the search. I wanted this feature so I could define a search that only returned all the *.mp3 files found in a directory.

Basics of the RecursiveFileExplorer Namespace

The very first thing you need is a brief explanation of the RecursiveFileExplorer Namespace and what it contains. Two classes are defined in the namespace:

public class FileExplorer
public class FileData

FileExplorer is the class you would implement to search a directory for files. FileData is the container class used by FileExplorer to store details of the files it finds. To use the RecursiveFileExplorer Namespace, simply include the following at the top of your file.

using RecursiveFileExplorer;

Using the FileExplorer Class

Using the FileExplorer class is deliberately simple. You simply pass it the path to the directory you want to search and it creates a collection of all the files in the directory in the form of an ArrayList object. This ArrayList can then be bound to a data control, or iterated through in some other fashion. The code to do this is as follows:

//Create FileExplorer object and search root directory
FileExplorer FileExplorer = new FileExplorer( "c:/" );
//Access and bind ArrayList to DataSource property of a Data Control
this.ResultsBox.DataSource = FileExplorer.FileList;

Now if you want to get the added functionality of recursing through the sub-directories as well, you need to use one of the other overloaded constructors that takes a boolean value that indicates whether to use recursion. Like so:

//Create FileExplorer object and search root directory
FileExplorer FileExplorer = new FileExplorer( "c:/", true );
//Access and bind ArrayList to DataSource property of a Data Control
this.ResultsBox.DataSource = FileExplorer.FileList;

To apply a filter to the files collated into the collection, use another of the other overloaded constructors that takes an ArrayList of file extensions. Like so:

//Create FileExplorer object and search root directory
ArrayList Extensions = new ArrayList();
Extensions.Add(".mp3");
Extensions.Add(".wav");
FileExplorer FileExplorer = new FileExplorer( "c:/", Extensions );
//Access and bind ArrayList to DataSource property of a Data Control
this.ResultsBox.DataSource = FileExplorer.FileList;

And of course, you can use all three options if you really want to wallow in excess:

//Create FileExplorer object and search root directory
ArrayList Extensions = new ArrayList();
Extensions.Add(".mp3");
Extensions.Add(".wav");
FileExplorer FileExplorer = new FileExplorer( "c:/", Extensions, true );
//Access and bind ArrayList to DataSource property of a Data Control
this.ResultsBox.DataSource = FileExplorer.FileList;

So what's the FileData Class for again...

When the FileExplorer collates the files it finds into a collection it actually stores more than just the fully qualified file name, it also stores the simple filename, the file extension and the length of the file in bytes, and it stores this data in an instance of the FileData class which it then adds to the ArrayList collection. This functionality is provided if you would like to extend the functionality of the class library. For instance I have an extension of this class library that stores ID2 metadata from mp3 files for easy access after the files have been searched through. Okay, so how do you access this information, well it's all pretty simple really:

//Create instance of FileData class by casting an element 
//of the ArrayList collection
FileData FileData = (FileData)FileExplorer.FileList[0];
//Access any members of the FileData object for display to screen
this.DisplayText.Text += "" + FileData.Length;

Code References

The test harness in the source files uses code from the article C# does Shell, Part 1 by Arik Poznanski to implement a dialog to select a folder. You would have thought the .NET Team would have added one in the Core NameSpace's wouldn't you. That would be too easy.

Conclusion

A quick and easy way to gather files from a computer into a collection that can be used in a larger application. The source files contain a class library project and a test harness project in a Visual Studio.NET 2002 solution. I just hope someone besides me can use it to speed up their development.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Andrew Boisen
Software Developer
Australia Australia
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralThank you! This saved me *MUCHO* time!memberdbraseth19 Dec '06 - 16:32 
Very nice class(es)! Thanks much.
GeneralSome error,, need helpmemberMd Saleem Navalur19 Apr '05 - 20:12 

If i click on the browse folder button,, I get this exception..
 
Unable to find an entry point named SHParseDisplayName in DLL shell32.dll.
 

Any inputs will be great.
 
saleem
GeneralA few enhancementsmemberD. Emilio Grimaldo Tuñon2 Dec '04 - 4:35 
Just what I was looking for considering I am short of time so here my comments:
 
1) Memory is indeed a main concern, but in my case my file sets are small so I can live with it until I find time to improve that.
 
2) I am also in favour of the strongly typed collection but opted not to use it just now.
 
3) I am not in favour of doing processing in the Constructor. I chose to rename getFiles to GetFiles and move that invocation out of the constructor and let do the class user do it whenever is wanted. See #4.
 
4) In my case I needed some extra filtering so I opted to implement a delegate/event that is called whenever a file is to be added. If the delegate is defined (not null) it is called. If the result of the delegate call is true the file is added, otherwise is it filtered out. A variation of extension but not related to it at all.
 
Anyway, nice work.
GeneralHere is a Strongly Typed FileDataCollection for your projectmemberJason33312 Sep '04 - 10:50 
I like your class. I did notice a comment on memory usage problems, and I think those types of issues can be a problem in some cases, but I think if you are using this solution for huge file listings, there is probably a better way to do things. Otherwise, this is a problem free/well done way to do things. I just thought I should make your FileData class have a strongly typed collection. Here it is:
 
using System;
using System.Collections;
 
namespace whatever
{


///
/// Strongly typed collection of FileData.
///

public class FileDataCollection : CollectionBase
{

///
/// Default constructor.
///

public FileDataCollection() :
base()
{
}

///
/// Gets or sets the value of the FileData at a specific position in the FileDataCollection.
///

public FileData this[int index]
{
get
{
return ((FileData)(this.List[index]));
}
set
{
this.List[index] = value;
}
}

///
/// Append a FileData entry to this collection.
///

/// FileData instance.
/// The position into which the new element was inserted.
public int Add(FileData value)
{
return this.List.Add(value);
}

///
/// Determines whether a specified FileData instance is in this collection.
///

/// FileData instance to search for.
/// True if the FileData instance is in the collection; otherwise false.
public bool Contains(FileData value)
{
return this.List.Contains(value);
}

///
/// Retrieve the index a specified FileData instance is in this collection.
///

/// FileData instance to find.
/// The zero-based index of the specified FileData instance. If the object is not found, the return value is -1.
public int IndexOf(FileData value)
{
return this.List.IndexOf(value);
}

///
/// Removes a specified FileData instance from this collection.
///

/// The FileData instance to remove.
public void Remove(FileData value)
{
this.List.Remove(value);
}

///
/// Returns an enumerator that can iterate through the FileData instance.
///

/// An FileData's enumerator.
public new FileDataCollectionEnumerator GetEnumerator()
{
return new FileDataCollectionEnumerator(this);
}

///
/// Insert a FileData instance into this collection at a specified index.
///

/// Zero-based index.
/// The FileData instance to insert.
public void Insert(int index, FileData value)
{
this.List.Insert(index, value);
}

///
/// Strongly typed enumerator of FileData.
///

public class FileDataCollectionEnumerator : object, IEnumerator
{

///
/// Current index
///

private int _index;

///
/// Current element pointed to.
///

private FileData _currentElement;

///
/// Collection to enumerate.
///

private FileDataCollection _collection;

///
/// Default constructor for enumerator.
///

/// Instance of the collection to enumerate.
internal FileDataCollectionEnumerator(FileDataCollection collection)
{
_index = -1;
_collection = collection;
}

///
/// Gets the FileData object in the enumerated FileDataCollection currently indexed by this instance.
///

public FileData Current
{
get
{
if (((_index == -1)
|| (_index >= _collection.Count)))
{
throw new System.IndexOutOfRangeException("Enumerator not started.");
}
else
{
return _currentElement;
}
}
}

///
/// Gets the current element in the collection.
///

object IEnumerator.Current
{
get
{
if (((_index == -1)
|| (_index >= _collection.Count)))
{
throw new System.IndexOutOfRangeException("Enumerator not started.");
}
else
{
return _currentElement;
}
}
}

///
/// Reset the cursor, so it points to the beginning of the enumerator.
///

public void Reset()
{
_index = -1;
_currentElement = null;
}

///
/// Advances the enumerator to the next queue of the enumeration, if one is currently available.
///

/// true, if the enumerator was succesfully advanced to the next queue; false, if the enumerator has reached the end of the enumeration.
public bool MoveNext()
{
if ((_index
< (_collection.Count - 1)))
{
_index = (_index + 1);
_currentElement = this._collection[_index];
return true;
}
_index = _collection.Count;
return false;
}
}
}
}
 

Then all you have to do is change your File explorer methods to use this:
 
FileDataCollection List = new FileDataCollection();
 
Instead of this:
ArrayList List = new ArrayList();
 
Have Fun!
GeneralNice but ...memberSébastien Lorion16 Aug '03 - 4:50 
Good job ! I think this can be useful, but there are a couple of points to consider :
 
  • Using delegates is far more efficient than filling a collection because usually, you don't need to keep all the files in memory.

    Eg. Using your class to recurse c:\ took about 30 MB before filling the list control !!

    You might want to take a look at the FindFile class I use in xmove.
  •  
  • If you do need to keep them in memory, then you would need to keep track of changes in the underlying filesystem using FileSystemWatcher.
  •  
  • If you need to display more than a couple of files (say 1000), it would be better to bind a grid control to a custom datasource. ListView is just not made to display that much content.
  •  
  • The TreeView/ListView interface is currently THE way to view file listings. Displaying full paths in a simple list is just lazy interface IMHO, unless when logging stuff, etc. You can use a FlexGrid instead of TreeView if you need more power.
 
Finally, I would change two thing in your class :
  • Make type-safe collection instead of ArrayList in FileExplorer
  • Make FileInfo available as a property of FileData instead of wrapping it partially
 
PS : SHParseDisplayName is only in shell32.dll version 6.0 and above == Windows XP. Though you might like to know this.
 


Intelligence shared is intelligence squared.
 
Homepage : http://www.slorion.webhop.org
GeneralRe: Nice but ...memberJason Webb24 Apr '05 - 21:37 
I agree. Using a stateful approach to anything causes memory overhead. The message to send to people is that they should only use a stateful approach, such as Collections (type safe or otherwise) when the application calls for it. I don't think this is one of those times seeing as how the ListView maitains the state of the data. I simply prefered to use a type safe Collection in my version of the application since there was a great deal of data manipulation/moving/sorting/etc involved, I was just sharing the class Smile | :) . Using a delegate or any function which simply sends the data to the ListView/GUI object would suffice in most cases. All this is really sort of overkill though since I think the point of this article is to show how to recurse a file system Wink | ;)

 
Happy Hunting
 
- Jason
GeneralI was looking for something like this...memberBrandon Haase14 Aug '03 - 6:45 
... a few days ago so I ended up rolling my own.
 
You bypassed a problem I encountered by checking the extensions of files directly:
 
One caveat to everyone about filtering the files using the build-in pattern matching in System.IO.Directory.GetFiles([path], [search pattern]) and System.IO.DirectoryInfo.GetFiles([search pattern]); if you specify a pattern for the file such as "*.vbp", it seems to return files with extensions as though you typed "*.vbp*".
 
In the case I ran into, "*.vbp" unexpectedly returned "*.vbproj" files as well!
 
Neatly done.
GeneralNicememberSteve McLenithan14 Aug '03 - 3:26 
Just what I was looking forCool | :cool:

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 13 Aug 2003
Article Copyright 2003 by Andrew Boisen
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid