假设我有这样的代码:
class Test
{
WeakReference m_ref;
public Test()
{
Test1();
Test2();
}
void Test1()
{
m_ref = new WeakReference(new object());
}
void Test2()
{
// If I do the GC then the m_ref.Target is null
// GC.Collect();
Debug.Log(m_ref.Target);
}
}
void TestFunc()
{
new Test();
}
在这个例子中,我创建了一个新的对象实例,并将其设置为WeakReference
中的Test1
实例。如果我在退出Test1
之后理解正确,那么对象实例就没有任何引用,所以这个实例很快就会成为GC
。
但是,在Test2
中如果没有执行GC
,我仍然可以通过m_ref.Target
访问对象实例。
有没有办法我可以知道m_ref.Target
无效而无需手动执行GC
?
不,你不能。根据设计,WeakReference
与垃圾收集器紧密耦合。甚至documentation也提到它:
获取当前WeakReference对象引用的对象是否已被垃圾回收的指示。
据我所知,C#中没有办法知道是否仍然存在对给定对象的活动引用,除非可以手动浏览整个引用树(并且几乎自己重新实现了GC)。
有没有办法在没有手动执行GC的情况下知道m_ref.Target是无效的?
在GC收集对象之前,它无效。垃圾收集的重点是您不知道,并且您不必在丢弃对象时进行处理。
在你的例子中,是的,你是正确的,在执行m_ref = new WeakReference(new object());
之后,将很快收集实例。然而,'很快'没有具体定义,所以你不能假设这将在Test2
被调用和Debug.Log(m_ref.Target);
执行之前发生。
如果在退出Test1后我理解正确,那么对象实例就没有任何引用...
你错了。从技术上讲,你没有任何创建对象的引用,它可能是下一行的gc'ed。
考虑这个简单的例子:
class Program
{
static WeakReference _ref;
static void Main(string[] args)
{
Test();
GC.Collect();
Console.WriteLine(_ref.IsAlive); // false
}
static void Test()
{
var obj = new object();
_ref = new WeakReference(obj);
GC.Collect();
Console.WriteLine(_ref.IsAlive); // true
}
}
在Test()
中,我们强烈地提到了一个对象,它确实持续到方法结束。
你可以做这样的事情来确定
object obj = _ref.Target;
if (obj != null)
{
... safe to do something with obj
}
您无法判断WeakReference是否有效。但你可以判断它是否无效。我知道这很奇怪。如果我有代码执行if语句并且可以询问“它是否有效”,那么它可能在下一行代码中无效,因此它是无用的。对WeakReference的TryGetTarget调用获取对该对象的引用,或者失败并返回false。一旦它有一个引用,引用就可以防止对象被垃圾回收,因此它至少与你有引用一样长久保持有效。
在某些代码中,可能会保留List <WeakReference <MyNiftyClass >>
跟踪该列表并保持其未被引用(其他地方)引用的一个好方法是让一些for循环使用TryGetTarget扫描列表,如果失败则从列表中删除过时引用。但是这样的删除会破坏迭代器,因此您希望将RemoveAll与谓词一起使用。我有一个名为CognateBase的类和一个名为AllCognateBases的静态全局列表。 CognateBase有一个Tick()函数,我每次调用程序时都会调用它。 Tick循环是一个收获过时参考的好地方。所以我有...
public static void TickAll()
{
// This will loop through all CognateBase objects and call their Tick, or if deleted from memory, remove the CognateBase.
AllCognateBases.RemoveAll(_TickIfAble);
}
然后_TickIfAble是
private static bool _TickIfAble(WeakReference<CognateBase> r)
{
CognateBase cb;
if (r.TryGetTarget(out cb))
{
cb.Tick();
return false;
}
else
{
return true;
}
}
因此,有效的CognateBase实例将被勾选,并且不再有效的那些实例将被删除。因为它在RemoveAll中没有任何迭代器搞砸了。
代码中我引用了CognateBase并将其设置为null的其他任何地方,最终将从列表中删除并删除CognateBase。
这个测试工作。所有这些GC调用都是为了强制垃圾收集现在发生,而不是在C#感觉它的时候。
public static void UnitTest()
{
Debug.Assert(AllCognateBases.Count == 0);
CognateBase b1 = new CognateBase();
Debug.Assert(AllCognateBases.Count == 1);
CognateBase b2 = new CognateBase();
Debug.Assert(AllCognateBases.Count == 2);
GC.Collect();
Debug.Assert(AllCognateBases.Count == 2);
b1 = null;
GC.Collect();
GC.WaitForFullGCComplete();
GC.WaitForPendingFinalizers();
TickAll();
GC.Collect();
GC.WaitForFullGCComplete();
GC.WaitForPendingFinalizers();
Debug.Assert(AllCognateBases.Count == 1);
b2 = null;
GC.Collect();
GC.WaitForFullGCComplete();
GC.WaitForPendingFinalizers();
TickAll();
GC.Collect();
GC.WaitForFullGCComplete();
GC.WaitForPendingFinalizers();
Debug.Assert(AllCognateBases.Count == 0);
}
而创作者......
public CognateBase()
{
AllCognateBases.Add(new WeakReference<CognateBase>(this));
}
警告 - 当引用设置为null时,如上所述b1 = null;对象可能不会在looooooong时间收集垃圾。所有这一切它仍然有效,并将得到它的Tick叫!