垃圾回收和对象初始值设定项

本文关键字:对象 | 更新日期: 2023-09-27 18:21:40

class Program
{
    static void Main(string[] args)
    {
        var test = new Test { Name = "One" };
        var test2 = new Test { Name = "Two" };
        var weak = new WeakReference<TestRef>(new TestRef(test, test2));
        var weak2 = new WeakReference<TestRef>(new TestRef(test) { Test2 = test2 });
        GC.Collect();
        TestRef tref;
        TestRef tref2;
        var @is = weak.TryGetTarget(out tref); //FALSE
        var @is2 = weak2.TryGetTarget(out tref2); //TRUE
    }
}
class Test
{
    public Test()
    { }
    public string Name { get; set; }
}
class TestRef
{
    public TestRef(Test test)
    { Test = test; }
    public TestRef(Test test, Test test2)
    {
        Test = test;
        Test2 = test2;
    }
    public Test Test { get; set; }
    public Test Test2 { get; set; }
}

在此示例中,如果在 GC 集合对象将从堆中删除后使用构造函数初始化对象,使用对象初始值设定项,引用将在收集后处于活动状态
为什么要@is == false@is2 == true
为什么weak2 GC.Collect()后仍然有对象,而weak却没有?

垃圾回收和对象初始值设定项

出现

"问题"是因为您对垃圾回收器的行为做出了 C# 语言规范未强制要求的假设。具体而言,您假定传递给 WeakReference 构造函数的每个对象都有资格在下一行进行垃圾回收。这不是一个正确的假设;事实上,C# 编译器实际上会产生两种不同的行为,具体取决于您是否启用了优化,并且没有一种比另一种更正确。

注意:以下评估是使用 Roslyn CTP for Visual Studio 2013 和 ILSpy 2.2.0.1706 执行的。

优化代码已禁用

启用优化后,中间值实际上存储在专门为其创建的临时局部变量中。特定的行是IL_0042: stloc.s 9行,它将对象存储在局部变量[9]中。此变量在使用后未设置为 null,因此引用实际上会阻止对象在 Main 方法返回之前进行垃圾回收。

.method private hidebysig static 
    void Main (
        string[] args
    ) cil managed 
{
    // Method begins at RVA 0x2050
    // Code size 112 (0x70)
    .maxstack 2
    .locals init (
        [0] class Test test,
        [1] class Test test2,
        [2] class [mscorlib]System.WeakReference`1<class TestRef> weak,
        [3] class [mscorlib]System.WeakReference`1<class TestRef> weak2,
        [4] class TestRef tref,
        [5] class TestRef tref2,
        [6] bool is,
        [7] bool is2,
        [8] class Test,
        [9] class TestRef
    )
    IL_0000: nop
    IL_0001: newobj instance void Test::.ctor()
    IL_0006: stloc.s 8
    IL_0008: ldloc.s 8
    IL_000a: ldstr "One"
    IL_000f: callvirt instance void Test::set_Name(string)
    IL_0014: nop
    IL_0015: ldloc.s 8
    IL_0017: stloc.0
    IL_0018: newobj instance void Test::.ctor()
    IL_001d: stloc.s 8
    IL_001f: ldloc.s 8
    IL_0021: ldstr "Two"
    IL_0026: callvirt instance void Test::set_Name(string)
    IL_002b: nop
    IL_002c: ldloc.s 8
    IL_002e: stloc.1
    IL_002f: ldloc.0
    IL_0030: ldloc.1
    IL_0031: newobj instance void TestRef::.ctor(class Test, class Test)
    IL_0036: newobj instance void class [mscorlib]System.WeakReference`1<class TestRef>::.ctor(!0)
    IL_003b: stloc.2
    IL_003c: ldloc.0
    IL_003d: newobj instance void TestRef::.ctor(class Test)
    IL_0042: stloc.s 9
    IL_0044: ldloc.s 9
    IL_0046: ldloc.1
    IL_0047: callvirt instance void TestRef::set_Test2(class Test)
    IL_004c: nop
    IL_004d: ldloc.s 9
    IL_004f: newobj instance void class [mscorlib]System.WeakReference`1<class TestRef>::.ctor(!0)
    IL_0054: stloc.3
    IL_0055: call void [mscorlib]System.GC::Collect()
    IL_005a: nop
    IL_005b: ldloc.2
    IL_005c: ldloca.s tref
    IL_005e: callvirt instance bool class [mscorlib]System.WeakReference`1<class TestRef>::TryGetTarget(!0&)
    IL_0063: stloc.s is
    IL_0065: ldloc.3
    IL_0066: ldloca.s tref2
    IL_0068: callvirt instance bool class [mscorlib]System.WeakReference`1<class TestRef>::TryGetTarget(!0&)
    IL_006d: stloc.s is2
    IL_006f: ret
} // end of method Program::Main

启用优化代码

启用优化后,您可以看到中间值仅存储在堆栈上;没有stloc指令用于将其放置在局部变量中。该功能由在线IL_0033 dup指令更有效地提供。运行此代码时,垃圾回收器很可能能够收集对象,因为在调用GC.Collect时它不再是线程堆栈的一部分。

.method private hidebysig static 
    void Main (
        string[] args
    ) cil managed 
{
    // Method begins at RVA 0x2050
    // Code size 86 (0x56)
    .maxstack 4
    .locals init (
        [0] class Test test2,
        [1] class [mscorlib]System.WeakReference`1<class TestRef> weak,
        [2] class TestRef tref,
        [3] class TestRef tref2
    )
    IL_0000: newobj instance void Test::.ctor()
    IL_0005: dup
    IL_0006: ldstr "One"
    IL_000b: callvirt instance void Test::set_Name(string)
    IL_0010: newobj instance void Test::.ctor()
    IL_0015: dup
    IL_0016: ldstr "Two"
    IL_001b: callvirt instance void Test::set_Name(string)
    IL_0020: stloc.0
    IL_0021: dup
    IL_0022: ldloc.0
    IL_0023: newobj instance void TestRef::.ctor(class Test, class Test)
    IL_0028: newobj instance void class [mscorlib]System.WeakReference`1<class TestRef>::.ctor(!0)
    IL_002d: stloc.1
    IL_002e: newobj instance void TestRef::.ctor(class Test)
    IL_0033: dup
    IL_0034: ldloc.0
    IL_0035: callvirt instance void TestRef::set_Test2(class Test)
    IL_003a: newobj instance void class [mscorlib]System.WeakReference`1<class TestRef>::.ctor(!0)
    IL_003f: call void [mscorlib]System.GC::Collect()
    IL_0044: ldloc.1
    IL_0045: ldloca.s tref
    IL_0047: callvirt instance bool class [mscorlib]System.WeakReference`1<class TestRef>::TryGetTarget(!0&)
    IL_004c: pop
    IL_004d: ldloca.s tref2
    IL_004f: callvirt instance bool class [mscorlib]System.WeakReference`1<class TestRef>::TryGetTarget(!0&)
    IL_0054: pop
    IL_0055: ret
} // end of method Program::Main