从终结器进行处理时,只读字段变为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 !
{
....
}
}
}
在垃圾收集上,未定义该收集的顺序:
- 收集第一个
this
- 收集下一个
disposeLock
或
- 收集第一个
disposeLock
- 收集下一个
this
因此,不要在Dispose(false);
上使用任何引用字段(结构,如int
、bool
等是安全的)
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非托管资源?