72

What's a good way to ensure that a temp file is deleted if my application closes or crashes? Ideally, I would like to obtain a temp file, use it, and then forget about it.

Right now, I keep a list of my temp files and delete them with an EventHandler that's triggered on Application.ApplicationExit.

Is there a better way?

Pang
  • 9,564
  • 146
  • 81
  • 122
Nifle
  • 11,745
  • 10
  • 75
  • 100
  • 3
    It's a shame .NET doesn't have something like Java's deleteOnExit() in the File class... not that it works properly if a file isn't closed. – Powerlord Dec 30 '08 at 13:45
  • I think FileOptions - DeleteOnClose will help to delete the fileStream on closing of fileStream. This option needs to be given at the time of fileStream creation https://learn.microsoft.com/en-us/dotnet/api/system.io.fileoptions?view=net-6.0 – Shankar S Jun 09 '22 at 09:57

9 Answers9

88

Nothing is guaranteed if the process is killed prematurely, however, I use "using" to do this..

using System;
using System.IO;
sealed class TempFile : IDisposable
{
    string path;
    public TempFile() : this(System.IO.Path.GetTempFileName()) { }

    public TempFile(string path)
    {
        if (string.IsNullOrEmpty(path)) throw new ArgumentNullException("path");
        this.path = path;
    }
    public string Path
    {
        get
        {
            if (path == null) throw new ObjectDisposedException(GetType().Name);
            return path;
        }
    }
    ~TempFile() { Dispose(false); }
    public void Dispose() { Dispose(true); }
    private void Dispose(bool disposing)
    {
        if (disposing)
        {
            GC.SuppressFinalize(this);                
        }
        if (path != null)
        {
            try { File.Delete(path); }
            catch { } // best effort
            path = null;
        }
    }
}
static class Program
{
    static void Main()
    {
        string path;
        using (var tmp = new TempFile())
        {
            path = tmp.Path;
            Console.WriteLine(File.Exists(path));
        }
        Console.WriteLine(File.Exists(path));
    }
}

Now when the TempFile is disposed or garbage-collected the file is deleted (if possible). You could obviously use this as tightly-scoped as you like, or in a collection somewhere.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 13
    It's not often you see a proper place to use an empty catch block. – Robert Rossney Dec 31 '08 at 20:01
  • I'll probably implement a variant of this. It's straight forward and easy to understand. – Nifle Jan 02 '09 at 08:44
  • is this this code can delete an temp files used by other applications – gasroot Jun 04 '13 at 08:49
  • 4
    @KevinPanko because I haven't taken the time to think about all the possible inheritance scenarios to determine whether it will behave as expected when subclassed. If you want to do that, feel free, – Marc Gravell Jun 24 '13 at 19:41
  • Is the finalizer needed? I thought System.IO.File is managed so you can use it without needing a finalizer? [Finalizers are] confusing, error-prone, and widely misunderstood. It has syntax very familiar to users of C++, but surprisingly different semantics. And in most cases, use of the feature is dangerous, unnecessary, or symptomatic of a bug. Reference: http://www.informit.com/articles/article.aspx?p=2425867 – PMBjornerud Aug 11 '16 at 11:18
  • 3
    @PMBjornerud yes, in this case; the purpose of the finalizer here has noting to do with the `File` object. It has to do with the intent of the class, which is (see the question) to **delete the file when done**. We want to do this *even if* somebody fails to `Dispose()` it correctly. We don't even *have* a `File` instance. – Marc Gravell Aug 11 '16 at 14:49
80

Consider using the FileOptions.DeleteOnClose flag:

using (FileStream fs = new FileStream(Path.GetTempFileName(),
       FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None,
       4096, FileOptions.RandomAccess | FileOptions.DeleteOnClose))
{
    // temp file exists
}

// temp file is gone
piedar
  • 2,599
  • 1
  • 25
  • 37
DanW
  • 1,976
  • 18
  • 14
  • This is the simplest/best solution in most cases; just let the OS take care of it. I edited the code to make it a little clearer and changed the buffer size to match the [FileStream default](http://referencesource.microsoft.com/#mscorlib/system/io/filestream.cs,398). BTW, I prefer `FileOptions.CreateNew` but I left `FileMode.OpenOrCreate` since you chose it purposefully. – piedar Jul 29 '16 at 02:26
  • 3
    The other process can't access file created even when FileOptions.DeleteOnClose is set and do not takes in account the FileShare.ReadWrite. So overall this code makes no sense to use as temp file. – Tomas Apr 09 '19 at 09:22
  • @Tomas Don't suppose you'd be able to elaborate on why this code snippet will not allow other process to access despite the DeleteOnClose? (I'm not disagreeing with you, as I have literally wrote this same using statement and just ran into the issue that a process still has a hold on the file hence me finding this thread!) I'm new to C# and can't seem to work out specifically what about this is causing that, and what the recommended alternative would be (use File.Create instead of new FileStream?). – Kieran Osgood Feb 24 '21 at 06:37
  • 1
    @piedar `Path.GetTempFileName()` already creates the file, so `FileOptions.CreateNew` will not work and throw an `IOException: The file ... already exists`. – Bouke Oct 13 '22 at 07:38
22

You could P/Invoke CreateFile and pass the FILE_FLAG_DELETE_ON_CLOSE flag. This tells Windows to delete the file once all handles are closed. See also: Win32 CreateFile docs.

David Grant
  • 3,447
  • 4
  • 29
  • 33
  • 11
    Be careful using this method if you need to open the file more than once (e.g. at a later time or by another process). For example, if you expect to open, populate, and close the file, it may vanish immediately so that you cannot use what you just created. You would have to leave the original handle open long enough for a 2nd handle to be opened, but that could cause a sharing violation if not opened with the appropriate flags. In .NET 4 (possibly earlier), just use File.Create( path, 0x1000, FileOptions.DeleteOnClose ); no need for P/Invoke. – Triynko Jan 09 '12 at 18:32
  • 6
    I wonder if there is a way to use FileOptions.DeleteOnClose in conjunction with System.Diagnostics.Process.Start(myFile) somehow. I would like to display the file and immediately delete after. – knockando Feb 24 '12 at 20:23
  • @knockando This is usually done by waiting for the process and deleting the file after it has finished. I don't see a way to do this with `DeleteOnClose`. – Paul Groke Sep 09 '15 at 16:39
10

I would use the .NET TempFileCollection class, as it's built-in, available in old versions of .NET, and implements the IDisposable interface and thus cleans up after itself if used e.g. in conjunction with the "using" keyword.

Here's an example that extracts text from an embedded resource (added via the projects property pages -> Resources tab as described here: How to embed a text file in a .NET assembly?, then set to "EmbeddedResource" in the embedded file's property settings).

    // Extracts the contents of the embedded file, writes them to a temp file, executes it, and cleans up automatically on exit.
    private void ExtractAndRunMyScript()
    {
        string vbsFilePath;

        // By default, TempFileCollection cleans up after itself.
        using (var tempFiles = new System.CodeDom.Compiler.TempFileCollection())
        {
            vbsFilePath= tempFiles.AddExtension("vbs");

            // Using IntelliSense will display the name, but it's the file name
            // minus its extension.
            System.IO.File.WriteAllText(vbsFilePath, global::Instrumentation.Properties.Resources.MyEmbeddedFileNameWithoutExtension);

            RunMyScript(vbsFilePath);
        }

        System.Diagnostics.Debug.Assert(!File.Exists(vbsFilePath), @"Temp file """ + vbsFilePath+ @""" has not been deleted.");
    }
Community
  • 1
  • 1
user3454591
  • 331
  • 3
  • 5
6

I use a more reliable solution:

using System.IO;
using System.Reflection;
 
namespace Helpers
{
    public static partial class TemporaryFiles
    {
        private const string UserFilesListFilenamePrefix = ".used-temporary-files.txt";
        static private readonly object UsedFilesListLock = new object();
 
        private static string GetUsedFilesListFilename()
        {
            return Assembly.GetEntryAssembly().Location + UserFilesListFilenamePrefix;
        }
 
        private static void AddToUsedFilesList(string filename)
        {
            lock (UsedFilesListLock)
            {
                using (var writer = File.AppendText(GetUsedFilesListFilename()))
                    writer.WriteLine(filename);
            }
        }
 
        public static string UseNew()
        {
            var filename = Path.GetTempFileName();
            AddToUsedFilesList(filename);
            return filename;
        }
 
        public static void DeleteAllPreviouslyUsed()
        {
            lock (UsedFilesListLock)
            {
                var usedFilesListFilename = GetUsedFilesListFilename();
 
                if (!File.Exists(usedFilesListFilename))
                    return;
 
                using (var listFile = File.Open(usedFilesListFilename, FileMode.Open))
                {
                    using (var reader = new StreamReader(listFile))
                    {
                        string tempFileToDelete;
                        while ((tempFileToDelete = reader.ReadLine()) != null)
                        {
                            if (File.Exists(tempFileToDelete))
                                File.Delete(tempFileToDelete);
                        }
                    }
                }
 
                // Clean up
                using (File.Open(usedFilesListFilename, FileMode.Truncate)) { }
            }
        }
    }
}

Every time you need temporary file use:

var tempFile = TemporaryFiles.UseNew();

To be sure all temporary files are deleted after application closes or crashes put

TemporaryFiles.DeleteAllPreviouslyUsed();

at start of the application.

Konard
  • 2,298
  • 28
  • 21
  • Or you can use [TemporaryFiles](https://github.com/linksplatform/IO/blob/master/csharp/Platform.IO/TemporaryFiles.cs) abstraction from our LinksPlatfrom's class library (available as [NuGet package](https://www.nuget.org/packages/Platform.IO)). – Konard Jan 31 '22 at 12:53
3

I'm not primarily a C# programmer, but in C++ I'd use RAII for this. There are some hints on using RAII-like behaviour in C# online, but most seem to use the finalizer — which is not deterministic.

I think there are some Windows SDK functions to create temporary files, but don't know if they are automatically deleted on program termination. There is the GetTempPath function, but files there are only deleted when you log out or restart, IIRC.

P.S. The C# destructor documentation says you can and should release resources there, which I find a bit odd. If so, you could simply delete the temp file in the destructor, but again, this might not be completely deterministic.

Pang
  • 9,564
  • 146
  • 81
  • 122
csl
  • 10,937
  • 5
  • 57
  • 89
  • The destructor is commonly used to release handles and resources on a per class basis. I imagine that you could use it to delete files, but that should be really be handled by the application code that created them. – StingyJack Dec 30 '08 at 13:09
  • Noob question: Does it have to be deterministic? To my knowledge, deterministic means that you do not know exactly WHEN it will run, but normally you can be sure that it WILL run (unless you yank the power cord of course) - or am I wrong here? – Michael Stum Dec 30 '08 at 13:25
  • How do you RAII expect to work when the process exits abnormally? The OP specifically asks "... or crashes". – MarkusSchaber Jun 21 '11 at 12:54
  • @MarkusSchaber: If it's an exception, RAII works fine, if you pull the power cord to the computer, it won't help. :) – csl Jun 21 '11 at 14:05
  • @csl: There are other ways (e. G. hard process kill) which prevent RAII from working. – MarkusSchaber Jun 21 '11 at 16:04
  • @MarkusSchaber: Exactly my point. If you are dead determined to delete those files, you could make a sentinel PID to do it for you, hook the file into Windows delete-on-warm-reboot or whatever. The OP said "closes or crashes", and "using" doesn't cover crashes either. – csl Jun 21 '11 at 18:23
  • @csl: Agree, but the answer pointing to FILE_FLAG_DELETE_ON_CLOSE comes pretty close to the goal. – MarkusSchaber Jun 27 '11 at 14:01
3

It's nice to see that you want to be responsible, but if the files aren't huge (>50MB), you would be in line with everyone (MS included) in leaving them in the temp directory. Disk space is abundant.

As csl posted, GetTempPath is the way to go. Users who are short on space will be able to run disk cleanup and your files (along with everyone else's) will be cleaned up.

Pang
  • 9,564
  • 146
  • 81
  • 122
StingyJack
  • 19,041
  • 10
  • 63
  • 122
  • 13
    Personally I think a temp file is a temp file, and should be destroyed as soon as it's no longer needed. I hate my disk being cluttered with all kinds of trash. Shameful that no-one (MS included) cares about my computer's state... – user39603 Dec 30 '08 at 13:17
  • 1
    Understood, It's commendable to try, but my point is that if you cant get everything, its not going to be bad. – StingyJack Dec 30 '08 at 13:38
  • 4
    Agreed.. if for whatever reason you can't clean up all files, they'd better be in the temp directory then anywhere else. – user39603 Dec 31 '08 at 08:37
  • I don't know why this answer's got so many down votes... What StingyJack suggested is almost the same thing that the C# garbage collector does. – Thomas C. G. de Vilhena Feb 17 '13 at 03:37
  • 3
    People don't like it when the truth of the situation doesn't match with their perfect ideals. – StingyJack Feb 18 '13 at 12:54
  • 2
    The trouble is Windows can't know when a temp file should be deleted, so it has to leave them on the disk, leaving it up to the programmer to remember to delete them. It would be good if they did remember! It's a pity more programmers don't use FileOptions DeleteOnClose – Matthew Lock Jan 14 '14 at 01:11
  • 5
    It's probably "OK" for most applications to leave temp files in place in the case of a sudden power loss. It might also be considered OK when the process is killed. It's certainly *not* OK tough to leave temp files in place if the application is neither killed nor interrupted by a power loss (or similar). – Paul Groke Sep 09 '15 at 16:43
0

You could launch a thread on startup that will delete files that exist when they "shouldn't" to recover from your crash.

Austin Salonen
  • 49,173
  • 15
  • 109
  • 139
-2

If you're building a Windows Forms Application, you can use this code:

    private void Form1_FormClosing(object sender, FormClosingEventArgs e)
    {
        File.Delete("temp.data");
    }
Zach
  • 35
  • 4