【问题标题】:How do I automatically delete temp files in C#?如何在 C# 中自动删除临时文件?
【发布时间】:2010-09-28 20:18:42
【问题描述】:

如果我的应用程序关闭或崩溃,有什么好的方法可以确保删除临时文件?理想情况下,我想获取一个临时文件,使用它,然后忘记它。

现在,我保留一个临时文件列表,并使用在 Application.ApplicationExit 上触发的 EventHandler 删除它们。

有没有更好的办法?

【问题讨论】:

  • 很遗憾 .NET 在 File 类中没有类似 Java 的 deleteOnExit() 之类的东西......如果文件没有关闭,它就不能正常工作。

标签: c# .net temporary-files


【解决方案1】:

如果进程被过早终止,则无法保证,但是,我使用“using”来执行此操作..

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));
    }
}

现在,当 TempFile 被处理或垃圾收集时,文件将被删除(如果可能)。显然,您可以随意使用它,或者在某个集合中使用它。

【讨论】:

  • 您通常不会看到使用空 catch 块的合适位置。
  • 我可能会实现它的一个变体。它直截了当,易于理解。
  • 这段代码是否可以删除其他应用程序使用的临时文件
  • @KevinPanko 因为我没有花时间考虑所有可能的继承场景来确定它在子类化时是否会按预期运行。如果你想这样做,请随意,
  • @PMBjornerud 是的,在这种情况下;这里终结器的目的与File 对象有关。它与类的意图有关,即(参见问题)完成后删除文件。我们希望这样做即使有人未能正确Dispose()。我们甚至没有 File 实例。
【解决方案2】:

考虑使用 FileOptions.DeleteOnClose 标志:

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

【讨论】:

  • 这是大多数情况下最简单/最好的解决方案;让操作系统来处理它。我编辑了代码以使其更清晰,并更改了缓冲区大小以匹配FileStream default。顺便说一句,我更喜欢FileOptions.CreateNew,但我离开了FileMode.OpenOrCreate,因为你是故意选择的。
  • 即使设置了 FileOptions.DeleteOnClose 并且不考虑 FileShare.ReadWrite,其他进程也无法访问创建的文件。所以总的来说,这段代码用作临时文件是没有意义的。
  • @Tomas 不要以为您能详细说明为什么尽管 DeleteOnClose 的代码 sn-p 不允许其他进程访问? (我并不反对你,因为我确实写过同样的 using 语句,只是遇到了一个进程仍然保留文件的问题,因此我找到了这个线程!)我是 C# 新手,可以似乎没有具体弄清楚这是什么原因造成的,以及推荐的替代方案是什么(使用 File.Create 而不是 new FileStream?)。
【解决方案3】:

您可以P/Invoke CreateFile 并传递FILE_FLAG_DELETE_ON_CLOSE 标志。这告诉 Windows 在所有句柄关闭后删除文件。另见:Win32 CreateFile docs

【讨论】:

  • 如果您需要多次打开文件(例如稍后或通过另一个进程),请小心使用此方法。例如,如果您希望打开、填充和关闭文件,它可能会立即消失,因此您无法使用刚刚创建的文件。您必须将原始句柄打开足够长的时间才能打开第二个句柄,但如果没有使用适当的标志打开,这可能会导致共享冲突。在 .NET 4(可能更早)中,只需使用 File.Create(path, 0x1000, FileOptions.DeleteOnClose);不需要 P/Invoke。
  • 我想知道是否有办法以某种方式将 FileOptions.DeleteOnClose 与 System.Diagnostics.Process.Start(myFile) 结合使用。我想显示文件并在之后立即删除。
  • @knockando 这通常通过等待进程并在完成后删除文件来完成。我看不到使用DeleteOnClose 的方法。
【解决方案4】:

我会使用 .NET TempFileCollection 类,因为它是内置的,在旧版本的 .NET 中可用,并实现了 IDisposable 接口,因此如果使用它会自行清理,例如与"using" 关键字一起使用。

这是一个从嵌入资源中提取文本的示例(通过项目属性页添加 -> 资源选项卡,如下所述:How to embed a text file in a .NET assembly?,然后在嵌入文件的属性设置中设置为 "EmbeddedResource")。

    // 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.");
    }

【讨论】:

    【解决方案5】:

    我使用更可靠的解决方案:

    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)) { }
                }
            }
        }
    }
    

    每次需要临时文件使用时:

    var tempFile = TemporaryFiles.UseNew();
    

    确保在应用程序关闭或崩溃后删除所有临时文件

    TemporaryFiles.DeleteAllPreviouslyUsed();
    

    在应用程序开始时。

    【讨论】:

    【解决方案6】:

    很高兴看到您愿意负责,但如果文件不是很大 (>50MB),您将与所有人(包括 MS)保持一致,将它们留在临时目录中。磁盘空间充足。

    作为csl posted,GetTempPath 是要走的路。空间不足的用户将能够运行磁盘清理,并且您的文件(以及其他所有人的文件)将被清理。

    【讨论】:

    • 我个人认为临时文件是临时文件,一旦不再需要就应该销毁。我讨厌我的磁盘被各种垃圾弄得乱七八糟。可耻的是没有人(包括 MS)关心我的计算机的状态......
    • 同意..如果由于某种原因您无法清理所有文件,最好将它们放在临时目录中,然后放在其他任何地方。
    • 人们不喜欢真实情况与其完美理想不相符的情况。
    • 问题是Windows不知道什么时候应该删除一个临时文件,所以它必须把它们留在磁盘上,让程序员记住删除它们。如果他们记得就好了!可惜更多的程序员不使用 FileOptions DeleteOnClose
    • 对于大多数应用程序来说,在突然断电的情况下保留临时文件可能是“可以的”。当进程被杀死时,它也可能被认为是好的。如果应用程序既没有因断电(或类似情况)而被杀死也没有中断,那么将临时文件留在原处肯定好。
    【解决方案7】:

    我主要不是 C# 程序员,但在 C++ 中,我会为此使用 RAII。网上有一些hints on using RAII-like behaviour in C#,但大多数似乎都使用终结器——这不是确定性的。

    我认为有一些 Windows SDK 功能可以创建临时文件,但不知道它们是否会在程序终止时自动删除。有GetTempPath 功能,但那里的文件只有在您注销或重新启动时才会被删除,IIRC。

    附: C# destructor documentation 说你可以而且应该在那里释放资源,我觉得这有点奇怪。如果是这样,您可以简单地删除析构函数中的临时文件,但同样,这可能不是完全确定的。

    【讨论】:

    • 析构函数通常用于在每个类的基础上释放句柄和资源。我想你可以用它来删除文件,但这应该由创建它们的应用程序代码来处理。
    • 新手问题:它必须是确定性的吗?据我所知,确定性意味着您不确切知道它何时会运行,但通常您可以确定它会运行(当然,除非您拉动电源线) - 还是我错了?
    • 当进程异常退出时,您希望RAII如何工作? OP 专门询问“……或崩溃”。
    • @MarkusSchaber:如果是例外,RAII 可以正常工作,如果您将电源线拉到计算机上,将无济于事。 :)
    • @csl:还有其他方法(例如硬进程终止)可以阻止 RAII 工作。
    【解决方案8】:

    您可以在启动时启动一个线程,该线程将删除“不应该”从崩溃中恢复而存在的文件。

    【讨论】:

      【解决方案9】:

      如果您正在构建 Windows 窗体应用程序,则可以使用以下代码:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-09-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-12-27
        • 1970-01-01
        相关资源
        最近更新 更多