# 前言

这两天又复习了下垃圾回收机制,在看到 析构函数 的时候,突然想到:析构函数是在真正垃圾回收时才会执行的,那么是否可以用这个来测试一个对象,什么情况下可以立即被垃圾回收?或者说变成垃圾对象。

例如,使用完毕后立即赋值为空,它变成垃圾对象了吗?还是其它什么时机才会。

测试方式为:在不同场景下,调用 GC.Collect ,并查看运行结果:析构函数是否被调用。

# 准备

工具:

  • VS2022
  • .Net6.0
  • 测试类:
class TestClass
{
    public void DoSomething()
    {
        Console.WriteLine("Doing!");
    }
    ~TestClass()
    {
        Console.WriteLine("被垃圾被回收了!");
    }
}

# 测试

# 1)情景一:方法内部定义对象使用后赋值为空

方法内立即调用 GC.Collect ()

class MainClass
{
    /// <summary>
    /// 执行测试
    /// </summary>
    public void DoTest()
    {
        TestMethod();
        Console.ReadKey();
    }
    void TestMethod()
    {
        TestClass test = new TestClass();
        test.DoSomething();
        test = null;
        GC.Collect();
    }
}

打印结果:

Doing!

析构函数未被调用。

将 GC.Collect () 方法调用移动到 DoTest () 方法的 TestMethod () 调用之后:

Doing!
被垃圾被回收了!

析构函数被调用了。

test = null 去掉,依然执行了析构函数。

# 结论:方法块内的代码,使用完毕后无论是否赋值为空,都不会立即变成垃圾,当方法块结束后才会自动成为垃圾对象,能够被垃圾回收处理。

# 2)情景二:全局变量方法内部使用后赋值为空

方法内立即调用 GC.Collect ()

class MainClass
{
    private TestClass _test;
    /// <summary>
    /// 执行测试
    /// </summary>
    public void DoTest()
    {
        TestMethod();
        Console.ReadKey();
    }
    void TestMethod()
    {
        _test = new TestClass();
        _test.DoSomething();
        _test = null;
        GC.Collect();
    }
}

打印结果:

Doing!

析构函数未被调用。

将 GC.Collect () 方法调用移动到 DoTest () 方法的 TestMethod () 调用之后:

Doing!
被垃圾被回收了!

析构函数被调用了。

# 结论:全局变量方法内部使用后赋值为空,在该方法结束后,才会变成垃圾对象。

# 3)情景三:委托方法中包含对象

class MainClass
{
    /// <summary>
    /// 执行测试
    /// </summary>
    public void DoTest()
    {
        Action callback = TestMethod;
        callback.Invoke();
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
    void TestMethod()
    {
        TestClass test = new TestClass();
        test.DoSomething();
    }
}

打印结果:

Doing!
被垃圾被回收了!

析构函数被调用了。

此处改成:

Action callback = () =>
{
    TestClass test = new TestClass();
    test.DoSomething();
};

结果一致。

# 结论:委托、匿名方法本身新建的局部变量,会按照正常的方法调用规则在调用结束后释放。

# 4)情景四:匿名方法中包含对象

class MainClass
{
    /// <summary>
    /// 执行测试
    /// </summary>
    public void DoTest()
    {
        TestMethod().Invoke();
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
    Action TestMethod()
    {
        TestClass test = new TestClass();
        //test.DoSomething();
        return () => test.DoSomething();
    }
}

打印结果:

Doing!

析构函数未被调用,对象生命周期应该变成 DoTest 的了。

测试方法如下,在调用 DoTest 方法处再进行 GC.Collect 调用:

MainClass main = new MainClass();
main.DoTest();
GC.Collect();
GC.WaitForPendingFinalizers();

则执行了析构函数。

缓存为类的全局变量:

Action callback;
    /// <summary>
    /// 执行测试
    /// </summary>
    public void DoTest()
    {
        callback = TestMethod();
        callback.Invoke();
        callback = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }

未执行析构函数,除非与上述一样在 DoTest 执行完毕后调用 GC.Collect。

# 结论:匿名方法中引用了局部变量,会增加局部变量对象生命周期,提升至委托的调用 (存放) 级别,规则与普通对象一致 —— 委托不被置为空,变量也会一直存活。

# 总结

在方法块内对某个对象赋值为空,不管是全局还是局部变量,该对象并不能立刻成为『垃圾』,至少等该方法块执行完毕后,才可能能够被回收。

若存在匿名方法使用了局部变量,其生存周期与委托绑定。