Sometimes you need to create temporary files. Usually, you can discard those temporary files by opening them in a fashion so they are deleted when they are closed. However, in some cases, you are dealing with a library or other class that is very picky about what you give it. Other times, you create an entire directory and want that directory to be deleted when you application is closed.
Whatever the case, there are several approaches to this. The first and most obvious (to me) was to try to use C#/.NET’s Disposable interface pattern. By creating a static List of those objects, we can ensure their Dispose()/Finalizers are run when the application is terminated (static variables and fields are disposed when the application is being torn down). Then the logic to delete and attempt to delete the file can be placed in the Dispose() method as needed. My implementation originally encountered problems with sharing violations- since the application, early on, may have many handles open. Primarily, this likely occurs because of the non-deterministic nature of how static objects are disposed; so if a File is opened and is in another static member, it might not have been disposed when our dispose method is called. As a result I’ve added a Delayed invoke method which, if the delete fails initially, will call itself again after a second (trying up to five times).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
/// <summary> /// Rudimentary class that is instantiated with the name of a folder, and deletes that folder when the class instance is disposed. /// </summary> public class DeletionHelper : IDisposable { //helper native methods. private static readonly Queue<DeletionHelper> QueuedDeletions = new Queue<DeletionHelper>(); private readonly String mDeleteThis = ""; public DeletionHelper(String deletefolder) { mDeleteThis = deletefolder; } public String DeleteThis { get { return mDeleteThis; } } [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] private static extern bool MoveFileEx( string lpExistingFileName, string lpNewFileName, MoveFileFlags dwFlags); public static void QueueDeletion(String FileOrDir) { var dh = new DeletionHelper(FileOrDir); QueuedDeletions.Enqueue(dh); } ~DeletionHelper() { Dispose(); } #region IDisposable Members private int delaycount; public void Dispose() { try { if (Directory.Exists(mDeleteThis)) { Debug.Print("Deleting folder:" + mDeleteThis); Directory.Delete(mDeleteThis, true); } else if (File.Exists(mDeleteThis)) { int AttemptCount = 0; try { AttemptCount++; Debug.Print("Deleting File:" + mDeleteThis); File.Delete(mDeleteThis); } catch (IOException e) { if (AttemptCount == 10) { return; //give up! } Thread.Sleep(250); } } } catch (IOException ioe) { delaycount++; if (delaycount > 5) { //schedule for reboot deletion. ScheduleRebootDeletion(); return; } DelayCall(new TimeSpan(0, 0, 0, delaycount), Dispose); } } private static void DelayInvokeThread(Object parameters) { var acquireparam = (Object[]) parameters; var useaction = acquireparam[1] as Action; var waittime = (TimeSpan) acquireparam[2]; var startdelay = (DateTime) acquireparam[3]; while (DateTime.Now - startdelay < waittime) { Thread.Sleep(0); } if (useaction == null) return; useaction(); } private static void DelayCall(TimeSpan waittime, Action routine) { var usethread = new Thread(DelayInvokeThread); usethread.Start(new Object[] {usethread, routine, waittime, DateTime.Now}); } private void ScheduleRebootDeletion() { try { if (!MoveFileEx(mDeleteThis, null, MoveFileFlags.DelayUntilReboot)) { } } catch (DllNotFoundException dlex) { } } #endregion [Flags] internal enum MoveFileFlags { None = 0, ReplaceExisting = 1, CopyAllowed = 2, DelayUntilReboot = 4, WriteThrough = 8, CreateHardlink = 16, FailIfNotTrackable = 32, } } |
This implementation also gives up after a few tries and then tries to schedule the file for reboot deletion. I considered a rather insane mechanic whereby the class would store a data file in the Temporary folder, then when first constructed (eg. static constructor) it can check for that file and either create and dispose a DeletionHelper for each filename stored in that data file, or add those files to the existing list for deletion when the application terminates. However, after considering it I figured such a feature might make things more complicated than necessary.
The other idea for automatic deletion would be to use the CreateFile() API with full share permissions, and pass the FILE_FLAG_DELETE_ON_CLOSE flag to it, then close that file in the Dispose method. Here is one possible implementation:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
public class DeletionHelperAPI : IDisposable { private const int FILE_FLAG_DELETE_ON_CLOSE = 0x4000000; private const int FILE_SHARE_READ = 0x1; private const int FILE_SHARE_WRITE = 0x2; private const int ACCESS_NONE = 0; private const int OPEN_EXISTING = 3; private const int FILE_SHARE_DELETE = 0x4; private List<DeletionHelperAPI> Subdeletors = new List<DeletionHelperAPI>(); [DllImport("kernel32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)] public static extern SafeFileHandle CreateFile( string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr SecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile ); SafeFileHandle FileHandle = null; public DeletionHelperAPI(String FileOrFolder) { //if it's a folder, create subobjects. if (Directory.Exists(FileOrFolder)) { //create a subdeletor for each File/folder in that file-folder. DirectoryInfo di = new DirectoryInfo(FileOrFolder); foreach (FileSystemInfo fsi in di.GetFileSystemInfos()) { Subdeletors.Add(new DeletionHelperAPI(fsi.FullName)); } } else { FileHandle = CreateFile(FileOrFolder, ACCESS_NONE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, IntPtr.Zero, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, IntPtr.Zero); } } public void Dispose() { if (Subdeletors != null) { foreach (var iterate in Subdeletors) { iterate.Dispose(); } } if (FileHandle != null) { FileHandle.Dispose(); } } } |
And so, that gives us two implementations. I currently use the first in BASeBlock for deleting the temporary files and folders that are sometimes created during start-up, particularly if it finds a Zip file (which may have additional content that it checks for). Since those extracted files may be used during the run, I use the class to make sure they are deleted when the Application exits; or at least make an effort to do so.
Have something to say about this post? Comment!