从终结器进行处理时,只读字段变为null

本文关键字:读字段 null 处理 | 更新日期: 2023-09-27 18:21:21

我有以下类。现在,有时lock语句抛出一个ArgumentNullException,在这种情况下,我可以在调试器中看到disposelock对象实际上是null。

正如我所看到的disposition是false,我知道该方法是从Finalizer触发的。

但这怎么可能发生呢?它被定义为只读,并在创建对象时获取其值。

PS:我知道这不是一个好的模式,但它是给定代码的一部分,我无法解释为什么它会变成空

public abstract class DisposableMarshalByRefObject : MarshalByRefObject, IDisposable
{
    private readonly object disposeLock = new object();

   /// </summary>
   ~DisposableMarshalByRefObject()
   {
       Dispose(false);
   }
   /// <summary>
   /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
   /// </summary>
   public void Dispose()
   {
       Dispose(true);
       GC.SuppressFinalize(this);
   }
   protected void Dispose(bool disposing) //disposing = false,=> finalizer
   {
       lock (disposeLock) //ArgumentNull Exception !
       {
           ....
       }
   }
}           

从终结器进行处理时,只读字段变为null

垃圾收集上,未定义该收集的顺序:

  1. 收集第一个this
  2. 收集下一个disposeLock

  1. 收集第一个disposeLock
  2. 收集下一个this

因此,不要在Dispose(false); 上使用任何引用字段(结构,如intbool等是安全的)

protected virtual void Dispose(bool disposing) {
  if (disposing) {
    // Explicit disposing: it's safe to use disposeLock 
    lock (disposeLock) {
      ...
    }
  } 
  else {
    // Garbage collection: there's no guarantee that disposeLock has not been collected
  }
}

除反射答案外,所有现有答案均为false。GC在收集对象时不会将引用设置为null。对象访问不会由于GC而意外失败。终结顺序未定义,但所有存在的对象引用都将继续存在并有效。

我对发生的事情的猜测是:构造函数在字段初始化之前就被中止了。这就留下了字段null。终结器后来发现它是这样的。

构造函数可以通过抛出异常或调用Thread.Abort(这是邪恶的)来中止。

在垃圾收集中,未定义该收集的顺序

集合的顺序不是可观察的(除非通过弱引用…)。最终化顺序是可观察的,但lock语句不能观察到,因为对象在最终化时不会失去同步能力。

由于您强调了readonly,所以需要对其进行一些澄清。运行时不会阻止readonly字段的修改。不管readonly从C#变成IL中的initonly

例如,您可以使用反射轻松修改readonly字段:

class A
{
    private readonly int bar;
    public A()
    {
        bar = 1;
    }
    public void Foo()
    {
        Console.WriteLine(bar);
    }
}
var a = new A();
a.Foo(); // displays "1"
a.GetType().GetField("bar", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(a, 2);
a.Foo(); // displays "2"

当然,这并不意味着每次都应该为null测试这些字段,但当readonly字段被修改时(您遇到过其中一个),可能会出现的情况。

作为旁注。你真的需要终结器吗?我的意思是,是否存在true非托管资源?