为什么在终结器中调用Dispose()导致ObjectDisposedException?

时间:2010-11-15 23:03:21

标签: c# nhibernate session transactions dispose

我有一个NHibernate存储库,如下所示:

public class NHibRepository : IDisposable
{
    public ISession Session { get; set; }
    public ITransaction Transaction { get; set; }

    // constructor
    public NHibRepository()
    {
        Session = Database.OpenSession();
    }

    public IQueryable<T> GetAll<T>()
    {
        Transaction = Session.BeginTransaction();
        return Session.Query<T>();
    }

    public void Dispose()
    {
        if (Transaction != null && Transaction.IsActive)
        {
            Transaction.Rollback(); // ObjectDisposedException on this line
        }

        Session.Close();
        Session.Dispose();
    }

    ~NHibRepository()
    {
        Dispose();
    }
}

当我像这样使用存储库时,运行正常:

using (var repo = new NHibRepository())
{
    Console.WriteLine(repo.GetAll<Product>().Count());
}

但是当我像这样使用它时,会抛出一个ObjectDisposedException

var repo = new NHibRepository();
Console.WriteLine(repo.GetAll<Product>().Count());

简单的解决方案是始终显式地删除存储库,但遗憾的是我无法控制使用存储库的某些类的生命周期。

我的问题是,即使我没有明确地呼叫Transaction,为什么Dispose()已被处理掉了?如果没有明确处理,我想让存储库自动清理它。

5 个答案:

答案 0 :(得分:6)

  

我的问题是,即使我没有明确调用Dispose(),为什么处理已经处理完了?

也许交易的终结者先跑了。请记住,死对象的终结器可以按任何顺序和任何线程运行,并且在最终确定之前无需正确初始化。如果您不理解所有终结器的规则,那么在尝试编写使用终结器的更多代码之前,请先了解它们。这是最难对付的事情之一。

它看起来好像你已经错误地实施了一次性模式,这将导致你一个悲伤的世界。阅读模式并正确完成;终结者不应该处置已经被处置的东西:

http://msdn.microsoft.com/en-us/magazine/cc163392.aspx

答案 1 :(得分:1)

你应该把GC.SuppressFinalize(this);在您的Dispose方法中,否则终结器将处置已经处置的对象。此外,几乎在所有情况下,只有非托管资源才需要终结器

答案 2 :(得分:1)

输掉终结者。 非常 .net中的几个课程需要一个终结者;通常情况下,唯一应该有终结器的.net 2.0或更高级别的类是那些存在的唯一理由围绕着它。

如果具有终结器的类拥有对其他某个对象的访问权限,则在运行终结器时将应用以下三个条件之一:

  1. 另一个对象已经有了终结器(如果有的话);没有必要处理它,因为它已经被处理掉了。
  2. 另一个对象有一个计划运行的终结器;没有必要处理它,因为它会自动处理。
  3. 对另一个对象的引用存在于其终结器正在运行的对象之外;这通常意味着不应该处理它。

终结者应该采取任何行动处理管理对象的唯一时间是外部引用可能存在,并且最终确定的对象的集合意味着应该处理另一个对象尽管该参考文献的存在。这是一种非常罕见的情况。

答案 3 :(得分:0)

CLR不会保证调用终结器的顺序。它会看到一组无根对象(例如,无法从任何GC根目录访问)并开始调用终结器。在对象图中有连接并不重要。可以按任何顺序调用终结器。终结器旨在清除非托管资源,而不是子对象。您需要重新考虑您的API。

答案 4 :(得分:0)

你应该有一个成员变量isDisposed,它将被Dispose()方法设置为true。然后在Dispose()命令的开头,只要它已经设置为true就返回。根据.NET白皮书,Dispose()可以多次执行而没有副作用,这是实现它的方法。

其次,使类密封(该类未正确实现Dispose模式以进行继承),并在Dispose()方法中将is GC.SuppressFinalize(this);放在isDisposed变量赋值之前。

public void Dispose()
{
   if (isDisposed) { return; }

   ....

   GC.SuppressFinalize(this);   
   isDisposed = true;
}