我的问题是,从 GC 的角度来看, if(origDog != null 和dogRef.Target != null 之间有什么区别吗?
With origDog
那么如果origDog
不为空(因为当dogRef.Target
被分配给它,那么它将继续不为空,直到它被重写或变得可收集。
With dogRef.Target != null
那么问题不在于那个调用——它会正常工作——而是在调用它和尝试使用它之间的时间。
(顺便说一句,虽然它需要更多的输入,但通常在堆栈上创建一个临时值而不是两次命中属性通常会稍微更有效。在调用属性时值得这样做的效率并不高打字更自然,但值得注意的是,如果不想创建临时的唯一原因dogRef
担心这会给应用程序带来额外的工作)。
来自评论:
如果将 Target 与 null 进行比较会获得强引用,直到范围结束
它不会,分配也不会。重要的是要认识到范围与可收集性无关。在代码中:
void SomeMethodWhichThereforeHasAScope()
{
Dog origDog = (Dog)dogRef.Target;
if (origDog != null)
{
Console.Write(dogRef.Target == null); // probably going to be false (though sometimes reads get reordered, so there's a chance that happens).
origDog.Bark();
}
Console.Write(dogRef.Target == null); // could be true or false
var sb = new StringBuilder("I'm a string that got referenced in a call to a method");
Console.Write(sb.ToString());
Console.Write(dogRef.Target == null); // even more likely to be true.
}
origDog
在第三次测试时处于范围内Target
但之后就不再使用它了。这意味着对堆栈和/或用于调用的寄存器中的对象的引用Bark
可能已用于其他用途(该方法中发生的工作越多,可能性就越大),这意味着如果 GC 启动,它可能找不到引用。
“范围”是关于你在哪里can使用变量。 GC 根据您所在的位置进行工作did用它。一旦停止使用它,GC 可能会回收它引用的对象。通常我们不在乎,因为我们在使用某些东西后就不再使用它(事实上),所以我们不会注意到。有另一个经过的参考WeakReference
但改变了这一点。
这就是为什么GC.KeepAlive()
存在。它实际上并没有做任何事情,它只是一种不会被优化的方法,因此,如果您想要将变量保留在范围内的唯一原因是一些不寻常的 GC 东西(WeakReference
属于“不寻常的 GC 东西”类别)意味着您可能想通过该变量以外的其他东西使用同一个对象,直到此后它才会被收集KeepAlive()
call.
假设,我不需要调用 Dog 类的方法,而只需要检查目标是否还活着。我应该始终将目标强制转换为类实例,还是可以检查 Target 是否为空?
检查它不为空就可以了。确实使用IsAlive
很好。问题在于IsAlive
纯粹是因为它可能会变成false
在未来的某个时刻。对于任何检查生命的方法来说都是如此。
(我唯一一次见到卢西亚诺·帕瓦罗蒂,他还活着。从那以后我就再也没有见过他。事实上,我上次见到他时他还完全活着,但这并不能阻止他现在已经死了。WeakReference.IsAlive
是完全相同的)。
顺便说一句,对于单个调用,以下内容是有效且方便的:
((Dog)dogRef.Target)?.Bark();
因为?.
运营商将dup
参考所以它类似于:
var temp = ((Dog)dogRef.Target)
if (temp != null)
temp.Bark();
所以很安全。
如果对象和您将要做的工作之间存在脱节,您可以使用:
var temp = dogRef.Target;
if (temp != null)
{
DoStuffHere();
GC.KeepAlive(temp); // temp cannot be collected until this returns.
}
正如上面所说,KeepAlive()
只是一种无操作方法,不允许编译器和抖动优化掉。因此,堆栈或寄存器中必须有一个引用才能传递给它,GC 会看到它而不是收集它。