WeakReference 在 .Net Framework 和 .Net Core 之间的行为不同

Mat*_*son 6 c# weak-references .net-4.8 .net-core-3.1

考虑以下代码:

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

#nullable enable

namespace ConsoleApp1
{
    class Program
    {
        static void Main()
        {
            var list    = makeList();
            var weakRef = new WeakReference(list[0]);

            list[0] = null;
            GC.Collect();
            GC.WaitForPendingFinalizers();

            Console.WriteLine(weakRef.IsAlive);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        static List<int[]?> makeList()
        {
            return new List<int[]?> { new int[2] };
        }
    }
}
Run Code Online (Sandbox Code Playgroud)
  • 无论是在 .Net Framework 4.8 上发布还是调试版本,该代码都会打印False.
  • 通过 .Net Core 3.1 上的发行版或调试版本,该代码将打印True.

是什么导致了这种行为差异?(这导致我们的一些单元测试失败。)

注意:我将列表初始化放入makeList()并关闭内联,试图使.Net Core版本与.Net Framework版本相同,但无济于事。


[编辑] 正如汉斯指出的,添加一个循环可以解决这个问题。

打印以下代码False

var list    = makeList();
var weakRef = new WeakReference(list[0]);

list[0] = null;

for (int i = 0; i < 1; ++i)
    GC.Collect();

Console.WriteLine(weakRef.IsAlive);
Run Code Online (Sandbox Code Playgroud)

但这会打印True

var list    = makeList();
var weakRef = new WeakReference(list[0]);

list[0] = null;

GC.Collect();
GC.Collect();
GC.Collect();
GC.Collect();

// Doesn't seem to matter how many GC.Collect() calls you do.

Console.WriteLine(weakRef.IsAlive);
Run Code Online (Sandbox Code Playgroud)

一定是某种奇怪的抖动问题......

Ser*_*rvy 5

仅仅因为某些东西被允许收集并不意味着它有义务尽快收集。虽然该语言规定允许 GC 确定不再读取局部变量,因此不将其视为根,但这并不意味着您可以依赖于上次读取局部变量后立即收集它的内容。

这不是运行时中定义的行为之间的一些变化,而是两个运行时中未定义的行为,因此它们之间的差异是完全可以接受的。