5

I have a situation where I need to save an uploaded HttpPostedFile to the server's disk, provide its full path to some code that will do something with the file on disk, then delete the file. I decided to make a proxy for working with the file. The proxy abstracts the details of saving the file to disk and deleting it once it is no longer being used. I implemented IDisposable in the proxy in order to treat the saved file like an unmanaged resource and ensure it gets deleted at some point. Of course, every time I try to implement IDisposable, I double check the pattern and discover dozens and questions and articles on the topic covering all the most complicated implementations.

I am thinking that most of these implementations are overkill for what I need, so I've implemented it much more simply. There's not much in my class; there's merely the saving of a file and a few public strings to allow access to the saved file through its file path. There's a Delete method for explicitly deleting the file and a Dispose method to call Delete if the client code doesn't. And lastly there's a finalizer that merely calls Dispose. The class is sealed. There are no members that implement IDisposable themselves. There are no considerable managed resources. In my estimation, there's no need to further meddle with Garbage Collection as the only important thing that needs to happen is the deletion of the file.

So my questions are these:

  1. Is there anything inherently wrong with dealing with saving and deleting a "temp" file in this fashion?
  2. Are there any problems with my below implementation of IDisposable, considering I don't need to bother with managed resource cleanup.

Please note that in my use case, the file must be saved to disk for another piece of code to work with it, and the file needs to be accessible using its file path, not by passing streams around or anything like that.

public sealed class TempFileProxy : IDisposable
{
    private bool disposed;

    public TempFileProxy(HttpPostedFile httpPostedFile)
    {
        this.disposed = false;
        this.FileName = httpPostedFile.FileName;
        this.Directory = AppSettings("TempFileDirectory");
        this.FullPath = $@"{this.Directory}\{this.FileName}";

        httpPostedFile.SaveAs(this.FullPath);
    }

    ~TempFileProxy()
    {
        this.Dispose();
    }

    public string FullPath { get; }

    public string Directory { get; }

    public string FileName { get; }

    public void Dispose()
    {
        if (this.disposed)
        {
            return;
        }

        this.disposed = true;
        this.Delete();
    }

    public void Delete()
    {
        if (File.Exists(this.FullPath))
        {
            File.Delete(this.FullPath);
        }
    }
}
bubbleking
  • 3,329
  • 3
  • 29
  • 49
  • 4
    This question could be suitable for [Code Review](http://codereview.stackexchange.com/help), as long as (a) your code works as intended, (b) your code is real code, rather than example code, and (c) your code is included in the body of the question. If you wish for a peer review to improve all aspects of your code, please post it on Code Review. – Heretic Monkey Apr 29 '16 at 21:17

2 Answers2

2

There is nothing wrong in deleting temporary files using IDisposable implementation. However, be more careful when deleting it - you don't want exception to be thrown during this operation (because file is in use for example). Also, suppress finalizer if you already disposed an object using regular Dispose call:

~TempFileProxy() { 
    Dispose(false);
}

public void Dispose() { 
    Dispose(true); 
}

private void Dispose(bool disposing)
{
    if (disposing)
    {
        GC.SuppressFinalize(this);                
    }
    if (this.FullPath != null)
    {
        try { 
            File.Delete(this.FullPath); 
        }
        catch { }
        this.FullPath = null;
    }
}
Evk
  • 98,527
  • 8
  • 141
  • 191
  • This brings one big point about IDisposable that I've never quite understood. If I don't want to clean up managed resources in Dispose, can't I just not deal with them, not suppress the finalizer and let garbage collection take care of it whenever it sees fit? Apparently the answer is no, as no examples ever show this, but I don't understand why it can't be done this way. – bubbleking May 02 '16 at 14:10
  • Why there is finalizer in first place? Because if user of your class _forgot_ to call Dispose - we are going to delete file when .NET will garbage collect instance of your class, this is the _only_ reason to have finalizer in your case. Now if user did not forget to call Dispose - calling finalizer is useless (even if finalizer does absolutely nothing - having it called still has significant perfomance costs), so we tell GC to supress it. Note that IDisposable is just convenient way to tell the user of the class that you hold some resources, this does not enforce anything. – Evk May 02 '16 at 14:21
  • So, what happens in the case where Dispose is called, the Finalizer is suppressed but I don't set any of the managed resources to null in the Dispose method? Would FullPath and the other strings simply linger around forever and never be garbage collected? – bubbleking May 02 '16 at 15:43
  • You do not need to dispose managed resources. FullPath is set to null in this example just to prevent multiple executions of Dispose (so that if I call Dispose twice - nothing will happen on second try). You can as well use boolean flag instead - same result. I see you has been confused a bit by thinking we "dispose" FullPath string here - we do not. So by supressing finalizer you just tell .NET to not execute code in finalizer, not to not garbage collect instance - it will be collected regardless of finalizers. – Evk May 02 '16 at 15:46
  • Okay, that makes sense to me. I guess I've been thrown off by so many educational sources that claim you can/should go ahead and clean up managed resources along with the unmanaged ones. My impression was that GC was responsible for coming along and cleaning up the object and its managed members, so that if I didn't clean them up, and I suppressed Finalize, then GC would never clean up those members. – bubbleking May 02 '16 at 15:54
1

I have a couple of problems here:

1) I have had trouble with File.Exists failing on certain external devices. I've gotten to the point I simply try to use the file and catch the exception if it's thrown.

2) File.Delete can throw. 2a) The file is in use by something. 2b) There's a phantom lock left behind. (Windows 8 XPS viewer, I'm looking at you!) 2c) Network problems.

Loren Pechtel
  • 8,945
  • 3
  • 33
  • 45