【问题标题】:Deleting open file opened by a stream删除流打开的打开文件
【发布时间】:2014-02-02 00:09:43
【问题描述】:

在下面的 Dispose 函数中,如果我们是从终结器调用的,我们不会释放我们的流实例。 (是的,我知道这是一个有点复杂的 Dispose 版本,它应该受到虚拟保护,但这是它在我引用的源代码中的编写方式,请参阅下面的 Essential C# 5.0 链接)

class Foo
{
    FileInfo File;
    Stream Stream;

    // yada yada yada more code goes here...    

    public void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (Stream != null)
            {
                Stream.Dispose();
            }
        }

        if (File != null)
        {
            File.Delete();
        }
    }
}

据我了解,这是由于对象的 GC 释放顺序不可预测,包括实例成员。

但是,上面的代码肯定会引发异常(我测试过),如果:

  1. 客户端开发者忘记直接调用 Dispose()
  2. 调用的终结器 Dispose(false)
  3. 尚未处理流

所以 File.Delete() 将失败并抛出 System.IO.IOException 异常。

所以我的问题是, 确保关闭流和删除文件的正确方法是什么? (即使开发人员忘记手动处置对象) 显然访问 Stream 成员(同时 disposing==false)是不安全的,所以必须有办法。

请注意,FileOptions.DeleteOnClose 不是一个选项。动机如下:我有一个类似于 Open's 和 Close's some file 的类,并且跨方法边界使用该文件(即读取数据),并且我不希望在实际使用结束之前删除该文件。

参考 Essential C# 5.0

谢谢, 关门

【问题讨论】:

  • 你可以随时实现Finalizer,它会被GC调用,以防Dev调用Dispose
  • 请注意,在 if (disposing) 之外,您不应该访问 FileInfo 等其他托管对象,因为不能保证它们尚未被释放。
  • 好的,这里有个思路,自己创建一个流类,把所有的函数调用转发到一个内部流,当你的流类完成后删除文件。
  • @Bodgan,这就是它在书中的实现方式:books.google.co.il/… 我猜你是对的
  • @Bodgan,可能是因为 FileInfo 不是可终结的类型,即使在终结期间也可以隐式访问它? MS 将 finalizable 定义为覆盖 Finalize 的类型,FileInfo 不符合此条件)。

标签: c# .net


【解决方案1】:

你为什么不直接实现一个推荐(微软)模式

// When implementing Dispose, mark it my implementing 
// IDisposable interface. It make you code more readable,
// allow to put using(...) {...}  etc.
class Foo: IDisposable {
  FileInfo File; // <- class, not struct
  Stream Stream; // <- class, not struct

  ...

  // Just useful thing, esp. while debugging
  public Boolean IsDisposed {
    get;
    private set;
  }

  // Since Foo is not a sealed class be ready that
  // it could be inherited: "protected virtual"
  protected virtual void Dispose(bool disposing) {
    // .Net often let you call Dispose many time, inc. on disposed instances; 
    // The same should do we
    if (IsDisposed)
      return;

    // You can access classes (not structs) in "if (disposing) {...}" only
    if (disposing) {
      if (!Object.ReferenceEquals(null, Stream))
        Stream.Dispose();

      if (!Object.ReferenceEquals(null, File))    
        File.Delete(); 
    }

    IsDisposed = true;
  }

  // MS recommendation
  public void Dispose() {
    Dispose(false);
    GC.SuppressFinalize(this);
  }

  // No finalizer here: finalizer just make you potential error
  // (resourse leak or Access Violation) unstable and so harder
  // to detect and correct.
}

每当开发者想要使用这个类时,他/她就简单地提出来

using (var foo = new Foo(...)) {
  ...
}

【讨论】:

  • 嗨,德米特里,感谢您的回复。我将使用 MS 推荐,但我的班级负责创建文件,我希望它也负责清理它们。该类的客户端使用 Open()、ReadAt(int pos) 和 Close() 来访问文件,但他们可能会忘记调用 Close(),因此我希望该类确保在尝试删除之前正确处理流文件。我想确保通过 Finalizer...
  • @Ofir Deri:如果您实现 IDisposable,您会说“应该处置实例”,并且可以在编译时检查此请求,例如通过代码分析。通常我们将任何 IDisposable 放入 using(...) {...} 在这种情况下,它是 .Net 调用 Dispose() 方法
  • 我知道“使用”语句的效果,尽管在我的情况下它不符合条件,因为我的客户端开发人员正在跨方法边界访问对象。看来我必须将责任交给开发人员,以记住关闭流(即调用 Close()),因为似乎没有其他方法可以确保正确处理流和从磁盘中删除文件。
  • 如果某个类拥有 Foo 实例(并将其传递给它的方法),该类也应该实现 IDisposable(Foo 本身就是一个很好的例子:它拥有 IDisposable 的 Stream)。 “公共类酒吧:IDisposable { Foo foo; ...”
  • 好吧,它不拥有它,它只是类的客户。
猜你喜欢
  • 2013-04-02
  • 2015-12-17
  • 1970-01-01
  • 1970-01-01
  • 2012-08-14
  • 2019-02-25
  • 2018-05-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多