C# Using IDisposable to clean up temp files
I have a FileUploader class that can optionally be given a zip file which it extracts to a temporary location and returns the file paths.
开发者_JAVA技巧From what I understood, implementing the IDisposable
interface on the FileUploader class and using the Dispose
method to remove all temp files would make the class clean itself up as soon as its reference fell out of context?
This doesnt seem to be the case though - can anyone explain how I might go about what Im trying to achieve?
UPDATE
To clarify, my code is: public ActionResult ImportFile()
{
FileUploader uploader = new FileUploader(ControllerContext, "file"); // where "file" is the posted form's file element
uploader.SaveOrExtractFilesToTempLocation();
foreach (string file in uploader.files)
{
try
{
// do some stuff
}
catch (Exception err)
{
// let the user know
}
}
return View();
}
Im trying to get the FileUploader to delete all temp files after the ImportFile() method has completed
"fell out of context" is unclear; you would have to be doing:
using(var file = new FileUploader(...)) {
// do the work here
}
Without using
, there is no special handling. This then gets translated by the compiler into something like:
var file = new FileUploader(...);
IDisposable tmp = file;
try {
// do the work here
} finally {
if(tmp != null) tmp.Dispose();
}
and it is this that causes the deterministic cleanup.
You'll need to implement IDisposable correctly. Like the class below.
using System.IO;
/// <summary>
/// Represents a temporary storage on file system.
/// </summary>
public sealed partial class TempStorage
: IDisposable
{
#region Constructor
private TempStorage()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="TempStorage"/> class.
/// </summary>
/// <param name="path">The path to use as temp storage.</param>
public TempStorage(string path)
{
this.Path = path;
this.Clear();
this.Create();
}
#endregion
#region Properties
private string Path
{
get;
set;
}
#endregion
#region Methods
private void Create()
{
try
{
if (!Directory.Exists(this.Path))
{
Directory.CreateDirectory(this.Path);
}
}
catch (IOException)
{
}
}
public void Clear()
{
try
{
if (Directory.Exists(this.Path))
{
Directory.Delete(this.Path, true);
}
}
catch (IOException)
{
}
}
#endregion
#region IDisposable
/// <summary>
/// An indicator whether this object is beeing actively disposed or not.
/// </summary>
private bool disposed;
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Throws an exception if something is tried to be done with an already disposed object.
/// </summary>
/// <remarks>
/// All public methods of the class must first call this.
/// </remarks>
public void ThrowIfDisposed()
{
if (this.disposed)
{
throw new ObjectDisposedException(this.GetType().Name);
}
}
/// <summary>
/// Releases managed resources upon dispose.
/// </summary>
/// <remarks>
/// All managed resources must be released in this
/// method, so after disposing this object no other
/// object is beeing referenced by it anymore.
/// </remarks>
private void ReleaseManagedResources()
{
this.Clear();
}
/// <summary>
/// Releases unmanaged resources upon dispose.
/// </summary>
/// <remarks>
/// All unmanaged resources must be released in this
/// method, so after disposing this object no other
/// object is beeing referenced by it anymore.
/// </remarks>
private void ReleaseUnmanagedResources()
{
}
private void Dispose(bool disposing)
{
if (!this.disposed)
{
/* Release unmanaged ressources */
this.ReleaseUnmanagedResources();
if (disposing)
{
/* Release managed ressources */
this.ReleaseManagedResources();
}
/* Set indicator that this object is disposed */
this.disposed = true;
}
}
#endregion
}
Then use the class inside a using-block in your main-method like this:
using(TempStorage myStorage = new TempStorage("C:\temp")
{
// rest of the main method here...
}
Dispose
is only automatic if you wrap the context with the using
keyword:
using (FileUploader uploader = new FileUploader(...))
uploader.UploadFiles();
You can implement finalizers so that if you forget to call Dispose
(which you really should not!) and call the Dispose
in your finalizer. This will be called when object is being garbage collected - but it is undeterministic i.e. no way to find out when it will be called.
But this is not guaranteed.
As others have already said, Dispose
is not called automatically unless the context is wrapped with the using
keyword. You could however implement both the Dispose and Finalize patterns (call the Dispose
method from a finalizer/destructor). That way, you have a fail-safe that kicks in and removes your temporary files even if your code does not call Dispose
directly (since the garbage collector will call the finalizer eventually).
This article illustrates the concept, as well as provide some insights on how finalization works.
Like everyone else said, your best bet is to wrap it in a using statement to force the Dispose method to be called. I believe this is how Microsoft reccomends to use anything that implements IDispose, at least based on their code analysis rules.
IDisposable is appropriate in cases where an object changes something outside itself in a way that must at some point be cleaned up before the object disappears. The creation of temp files would seem to qualify. There are, however, a couple caveats:
- Unfortunately, there's no nice way for a disposer to determine whether an exception is pending when it runs. If I had my druthers, there would be an IDisposeableEx interface which would inherit IDisposable but also implement a Dispose(Ex as Exception) overload; this would behave like IDisposable except that Ex would be passed as an InnerException to any exception the IDisposable itself might throw. As it is, no such thing exists. Thus one may often face the uncomfortable choice of swallowing an exception that occurs while disposing, or burying an exception which occurred prior to disposing (and may have predicated the disposal in the first place). Both choices stink.
- Finalizers must avoid doing anything that might block or throw an exception. For something like file cleanup, it may be helpful to have a file-cleanup thread (for a group of classes, not one per object!) that waits for a finalizer to give it a signal and then cleans up files. If an attempt to clean up a file blocks, the file-cleanup thread may be blocked, but it wouldn't jinx the entire application.
I don't particularly like finalizers; the focus should be on making the disposer work.
精彩评论