Using The Recycle Bin From CSharp
(by John Roberts for Blayd Software)
Note: this article relates to the .Net Framework version 2.0, some
of the classes and class members referenced in this article were not available in
earlier framework versions.
Introduction
When developing applications that provide facilities that allow the user to perform
operations on file system objects, such as files and folders, we would all like
our applications to provide the user with the functionality that they are familiar
with. When copying or moving a large number of folders and/or files or a small number
of very large folders and/or files, most users will expect an application to report
the progress of the operation and may also require the application to provide them
with the means to cancel the operation. When deleting a folder or a selection of
files, most users will expect to, at least, have the option of sending the deleted
folder and/or files to the system Recycle Bin.
The .Net Framework System.IO namespace provides types that can be used to retrieve
information about file system folders and files and methods that we can use to copy,
move and delete folders and files. However, there is no built-in support for displaying
the progress of a copy, move or delete operation and there is no support for sending
folders or files to the system Recycle Bin. When performing file system copy, move
and delete operations silently, as some applications are required to do, this is
not a problem but in situations where we want to provide a UI that allows the user
to copy, move and delete file system folders and files, progress reporting, the
option to cancel and non-permanent deletion are all required.
The functionality that we require is, obviously, available in the operating system
and can be seen in action in Windows (file) Explorer and as you might expect this
functionality is made available to developers through the API or more precisely,
in this particular case, the shell API. The SHFileOperation API method provides
functionality for copying, moving, renaming and deleting file system objects and
provides progress reporting and Recycle Bin support. Therefore, CSharp developers
can make use of "pinvoke" to call in to the unmanaged API to get file copy and
move progress reporting and Recycle Bin support for their file system operations.
The SHFileOperation API might provide the functionality that we CSharp developers
require but it comes at a price. For a start, we would prefer to keep all of our
code in the managed environment if possible, also the SHFileOperation API and its
associated SHFILEOPSTRUCT struct can be pretty daunting to use if you have little
or no experience of Windows API programming. It is also worth noting that the SHFileOperation
API has been superseded in Windows Vista by the IFileOperation interface, although
the SHFileOperation function is still available and indeed should still be used
in preference to the IFileOperation interface in multi-threaded apartment (MTA)
situations.
Calling out to the Windows shell API is a possible but slightly less than ideal
solution but is there another way? Well, yes there is, although you may, at first,
think we have gone crazy, so bear with us, the functionality we require is contained
in one of the Visual Basic language utility libraries. The Microsoft.VisualBasic.FileIO
namespace contains a class named FileSystem that provides exactly what we are looking
for, folder and file copy and move with progress reporting and an option to cancel
and folder and file delete with Recycle Bin support.
Should or indeed can CSharp developers use a Visual Basic utility library? Yes they
can, one of the main strengths of the .Net Framework is the ability to call a library
written in one (.Net) language from an application written in a different (.Net)
language. All (.Net) source code, no matter what (.Net) language it is written in,
is compiled in to Microsoft intermediate language (MSIL), therefore, once compiled,
all languages produce compatible MSIL that can be executed, after native code compilation,
by any other .Net language. In addition, the Microsoft.VisualBasic.dll assembly,
that contains the Microsoft.VisualBasic.FileIO namespace, is part of the .Net Framework,
so we can be sure that it will be available to our applications.
The fact that Visual Basic developers get file operation progress reporting and
Recycle Bin support and CSharp develops do not raises a couple of interesting points.
Firstly, why is this the case? We don't have the answer to that one, however, it
does illustrate that although all .Net languages are, roughly, equal once compiled,
each language has its own strengths and weaknesses when it comes to ease of use
and supporting infrastructure. The second point of interest is, how does the Visual
Basic language utility library implement the file operation progress reporting and
Recycle Bin support? As you may have guessed, it uses the Windows shell API SHFileOperation
function! The relevant FileSystem class methods just wrap calls to the Windows shell
API, therefore, when we use these methods we are, ultimately, making a call to the
unmanaged API. However, the code making the API call is not ours, we don't have
to write, test or maintain it, we don't have to worry about future changes or compatibility
issues in the API, our code remains firmly in the managed world of the .Net Framework's
runtime environment. In our opinion this is much better than calling the API directly
from our own code, we assume that Microsoft's developers know more about the Windows
API than we do and we hope that they will continue to ensure that the FileSystem
.Net Framework type will continue to function as expected, regardless of any changes
that may occur in the API. You, of course, may feel differently, you may prefer
to call the Windows shell API directly from your own code and remove the overhead
incurred by calling the FileSystem wrapper.
Using the Visual Basic FileSystem class rather than calling the SHFileOperation
API function directly from CSharp code has one, potential, drawback. The FileSystem
class only provides functionality to copy, delete or move a single file at a time.
You can copy, delete or move a directory and its contents i.e. sub-directories and/or
files but you can't specify multiple files in calls to the FileSystem class CopyFile,
DeleteFile or MoveFile methods. The SHFileOperation shell API function accepts a
null delimited list of source and destination file paths in its SHFILEOPSTRUCT parameter
but there is no way to duplicate this behaviour in the Visual Basic FileSystem class.
You can, of course, copy, delete or move a list of files one at a time in successive
calls to the relevant FileSystem method but it does not make much sense to display
the progress dialog when using this technique, therefore, you loose this, useful,
functionality.
We will show some of the source code relating to the article, however a lot of the
method implementations are very similar, so we won't list all of the code. If you
are interested in or require a full source code listing, you can download a demonstration
Visual Studio 2005 CSharp project using the link provided in the "Article Options"
on the left of the page.
Wrapping the Wrappers
As mentioned earlier, the Visual Basic utility class that we are interested in is
named FileSystem and is located in the Microsoft.VisualBasic.FileIO namespace. The
types located in this namespace are intended to support the "My" runtime object
syntax available to Visual Basic developers. For example, to call a method in the
FileSystem class, a Visual Basic developer may use code similar to the following:
My.Computer.FileSystem.CopyFile("C:\SomeFile.txt", "C:\Test\SomeFile.txt")
Although the "My" runtime object syntax provided in Visual Basic is an interesting
idea it is not really relevant to this article, which is aimed at CSharp developers,
so we are not going to discuss it here. As CSharp developers we can ignore the "My"
syntax and the proxy objects used to provide access, for Visual Basic developers,
to the FileSystem class. To use the FileSystem class in CSharp code, we just need
to do the following:
- Add a project reference to the
Microsoft.VisualBasic.dll assembly
- Add a
using Microsoft.VisualBasic.FileIO; statement in the relevant source code
file(s)
There a couple of points worth noting when using the FileSystem class, firstly,
you should only use fully-specified absolute paths, rather than relative paths.
Secondly, we are going to treat the FileSystem class as a static class, therefore
we are going to be calling methods on the class rather than an instance of the class.
All of the methods and properties of the FileSystem class are static but the class
does define an instance constructor, however, for our requirements it makes sense
to treat the FileSystem class as a static utility class. When using the FileSystem
class from CSharp code, the equivalent call to the Visual Basic example above would
be similar to the following:
FileSystem.CopyFile("C:\\SomeFile.txt", "C:\\Test\\SomeFile.txt");
If you are only going to call the FileSystem class from one or maybe two places
in your CSharp project and you only intend to use this technique in a single project,
then adding the required using statement and calling the FileSystem class directly
may be fine. However, if you are going to use this technique in many places in your
project or in many projects, repeatedly referencing a Visual Basic namespace and
using Visual Basic defined types from CSharp code is not ideal and is likely to
be confusing to developers maintaining or extending the code in the future.
We have opted to create a CSharp developer friendly implementation by encapsulating
the Visual Basic defined namespace reference and all references to Visual Basic
defined types in our own CSharp utility class. This does mean that we are adding
a wrapper around the FileSystem method calls, which are themselves wrappers around
API calls but it does allow us to keep all of the Visual Basic related references
in a single place. Our CSharp developer friendly wrapper is a static class named
CsFileSystem. To use the CsFileSystem class, a developer just has to add a project
reference to the Microsoft.VisualBasic.dll assembly and then add the CsFileSystem
class to their project. Calling code does not need any Visual Basic namespace references
and does not need to know anything about or use any Visual Basic defined types.
Creating the static CsFileSystem class to call the relevant methods of the Visual
Basic defined FileSystem class is very straight forward apart from one small issue.
The FileSystem methods that we are primarily interested in are the ones that enable
us to display a progress dialog and cancel option when copying, deleting and moving
folders and files and the delete folder and file methods that have Recycle Bin support.
All of these methods make use of one or more of the following Visual Basic defined
enumerations:
- RecycleOption
- Specifies whether a folder or file should be deleted permanently or sent to the
Recycle Bin.
- UICancelOption
- Specifies whether an exception is thrown if the user clicks Cancel during an operation.
- UIOption
- Specifies whether the progress dialog and error dialogs or just error dialogs are
displayed during an operation.
Requiring calling CSharp code to use these enumerations defeats one of the main
objectives of creating the CsFileSystem wrapper class i.e. calling code would require
the Visual Basic defined namespace reference and would be referencing Visual Basic
defined types. We could get around this issue by providing our own enumerations
for use as method parameters, however, we would then have to convert the parameter
values to the Visual Basic equivalents before delegating the method call to the
FileSystem class. We decided against this solution and opted instead for an ever
easier solution, however, it does come with a caveat that will be explained after
we have outlined the chosen solution. Each of the enumerations above has two members
and each option can be specified as a Boolean choice e.g. delete permanently or
send to Recycle Bin, throw exception on cancel or don't throw an exception and show
progress dialogs or don't show progress dialogs. Therefore we have chosen the very
simple solution of exposing these options as Boolean parameters of the relevant
CsFileSytem class methods. Using Boolean parameter values is a simple way around
the issue but it is not without risk, if any of the enumerations is extended at
some time in the future it will no longer be possible to express the option as a
Boolean choice. We think that using Boolean parameters is an acceptable risk as
we don't think that the various options and therefore the enumerations are likely
to be extended, however, we may be proved wrong!
Copying a Folder or File
The CsFileSystem class contains a static CopyDirectory and a static CopyFile method,
each of which has three overloads. The signatures of the respective CopyDirectory
and CopyFile overloads are identical and the implementation is almost identical
up to the point at which control is delegated to the Visual Basic FileSystem class.
Rather than show a succession of almost identical source code we will use the CopyDirectory
method overload that actually calls the Visual Basic FileSystem class as an example.

CsFileSystem.CopyDirectory method listing:
public static void CopyDirectory(string source, string destination,
bool showProgress, bool throwOnCancel)
{
if (string.IsNullOrEmpty(source))
{
throw new ArgumentNullException("source");
}
if (string.IsNullOrEmpty(destination))
{
throw new ArgumentNullException("destination");
}
UIOption option = GetUIOption(showProgress);
UICancelOption cancelOption = GetUICancelOption(throwOnCancel);
FileSystem.CopyDirectory(source, destination, option, cancelOption);
}
Callers need to specify the absolute path of the source directory and destination
directory and can also use the destination parameter to rename the directory during
the copy operation. If the showProgress parameter is set to true, a progress reporting
dialog with a cancel option will be displayed during the copy operation. If showProgress
is set to false, only error dialogs will be displayed (if one or more errors occurs
during the copy operation). If the throwOnCancel parameter is set to true, an exception
will be thrown if the user cancels the operation, when set to false an exception
is not thrown on cancellation.
Multiple exceptions may occur when copying a directory and its contents. When there
are multiple exceptions they are rolled up into an IDictionary that is assigned
to the Data property of the thrown exception. Each entry is keyed on the path of
the source of the exception and the corresponding value contains the exception message.
The UIOption, UICancelOption and FileSytem types are all
defined in the Microsoft.VisualBasic.FileIO namespace.
Deleting a Folder or File
The CsFileSystem class contains a static DeleteDirectory and a static DeleteFile
method, each of which has three overloads. The signatures of the respective DeleteDirectory
and DeleteFile overloads are identical and the implementation is almost identical
up to the point at which control is delegated to the Visual Basic FileSystem class.
Rather than show a succession of almost identical source code we will use the DeleteDirectory
method overload that actually calls the Visual Basic FileSystem class as an example.

CsFileSystem.DeleteDirectory method listing:
public static void DeleteDirectory(string directory, bool showProgress,
bool toRecycleBin, bool throwOnCancel)
{
if (string.IsNullOrEmpty(directory))
{
throw new ArgumentNullException("directory");
}
UIOption option = GetUIOption(showProgress);
RecycleOption recycleOption = GetRecycleOption(toRecycleBin);
UICancelOption cancelOption = GetUICancelOption(throwOnCancel);
FileSystem.DeleteDirectory(directory, option, recycleOption, cancelOption);
}
Callers need to specify the absolute path of the directory. If the showProgress
parameter is set to true, a confirmation and a progress reporting dialog with a cancel option will
be displayed during the delete operation. If showProgress is set to false, only
error dialogs will be displayed (if one or more errors occurs during the delete
operation). If the throwOnCancel parameter is set to true, an exception will be
thrown if the user cancels the operation, when set to false an exception is not
thrown on cancellation.
Multiple exceptions may occur when deleting a directory and its contents. When there
are multiple exceptions they are rolled up into an IDictionary that is assigned
to the Data property of the thrown exception. Each entry is keyed on the path of
the source of the exception and the corresponding value contains the exception message.
The UIOption, RecycleOption, UICancelOption and FileSytem types are all defined
in the Microsoft.VisualBasic.FileIO namespace.
Moving a Folder or File
The CsFileSystem class contains a static MoveDirectory and a static MoveFile method,
each of which has three overloads. The signatures of the respective MoveDirectory
and MoveFile overloads are identical and the implementation is almost identical
up to the point at which control is delegated to the Visual Basic FileSystem class.
Rather than show a succession of almost identical source code we will use the MoveDirectory
method overload that actually calls the Visual Basic FileSystem class as an example.

CsFileSystem.MoveDirectory method listing:
public static void MoveDirectory(string source, string destination,
bool showProgress, bool throwOnCancel)
{
if (string.IsNullOrEmpty(source))
{
throw new ArgumentNullException("source");
}
if (string.IsNullOrEmpty(destination))
{
throw new ArgumentNullException("destination");
}
UIOption option = GetUIOption(showProgress);
UICancelOption cancelOption = GetUICancelOption(throwOnCancel);
FileSystem.MoveDirectory(source, destination, option, cancelOption);
}
Callers need to specify the absolute path of the source directory and destination
directory and can also use the destination parameter to rename the directory during
the move operation. If the showProgress parameter is set to true, a progress reporting
dialog with a cancel option will be displayed during the move operation. If showProgress
is set to false, only error dialogs will be displayed (if one or more errors occurs
during the move operation). If the throwOnCancel parameter is set to true, an exception
will be thrown if the user cancels the operation, when set to false an exception
is not thrown on cancellation.
Multiple exceptions may occur when moving a directory and its contents. When there
are multiple exceptions they are rolled up into an IDictionary that is assigned
to the Data property of the thrown exception. Each entry is keyed on the path of
the source of the exception and the corresponding value contains the exception message.
The UIOption, UICancelOption and FileSytem types are all
defined in the Microsoft.VisualBasic.FileIO namespace.
Helper Methods
The CsFileSystem class contains three static helper methods that are used to translate
the Boolean parameter values in to the relevant enumerated type value. All of these
methods have an identical implementation that is used to translate a specified Boolean
value in to the relevant enumerated value, so we will use the GetRecycleOption method
as an example.

CsFileSystem.GetRecycleOption helper method
listing:
private static RecycleOption GetRecycleOption(bool toRecycleBin)
{
return (toRecycleBin) ?
RecycleOption.SendToRecycleBin :
RecycleOption.DeletePermanently;
}
The RecycleOption enumerated type is defined in the Microsoft.VisualBasic.FileIO
namespace.
Conclusion
Using the functionality contained in a Visual Basic library is perhaps not the first
thing that springs to mind when CSharp developers are trying to find a solution
to a problem but as in the case demonstrated in this article it is possible and
it can save a lot of work. Apart from the frustrating omission of multiple file
copy, delete and move support, the Visual Basic FileSystem class provides some very
useful functionality for CSharp developers for very little effort.
Copyright ©Blayd Software Limited 2008-2010. All rights reserved.