WeakReference ทำงานแตกต่างกันระหว่าง. Net Framework และ. Net Core
พิจารณารหัสต่อไปนี้:
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] };
}
}
}
- ด้วยรีลีสหรือดีบักบิลด์บน. 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);
แต่สิ่งนี้จะพิมพ์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);
นี้ได้มีจะเป็นชนิดของสิ่งอัดฉีดบางลาง ...
คำตอบ
เพียงเพราะบางสิ่งบางอย่างได้รับอนุญาตให้รวบรวมไม่ได้หมายความว่าจำเป็นต้องรวบรวมโดยเร็วที่สุด ในขณะที่ภาษาระบุว่า GC ได้รับอนุญาตให้ตรวจสอบว่าตัวแปรโลคัลจะไม่ถูกอ่านอีกดังนั้นจึงไม่ถือว่าเป็นรูทนั่นไม่ได้หมายความว่าคุณสามารถพึ่งพาเนื้อหาของตัวแปรในเครื่องที่รวบรวมได้ทันทีหลังจากที่คุณอ่านครั้งสุดท้าย .
นี่ไม่ใช่การเปลี่ยนแปลงบางอย่างระหว่างพฤติกรรมที่กำหนดไว้ในรันไทม์นี่คือพฤติกรรมที่ไม่ได้กำหนดในช่วงเวลาทั้งสองดังนั้นความแตกต่างระหว่างพฤติกรรมเหล่านี้จึงยอมรับได้
ฉันได้รับการอ้างอิงที่จะเป็นอิสระเมื่อฉันลบตัวแปรรายการ:
using NUnit.Framework;
using System;
using System.Collections.Generic;
namespace NUnitTestProject1
{
public class Tests
{
[TestCase(2, GCCollectionMode.Forced, true)]
public void TestWeakReferenceWithList(int generation, GCCollectionMode forced, bool blocking)
{
static WeakReference CreateWeakReference()
{
return new WeakReference(new List<int[]> { new int[2] });
}
var x = CreateWeakReference();
Assert.IsTrue(x.IsAlive);
GC.Collect(generation, forced, blocking);
Assert.IsFalse(x.IsAlive);
}
}
}
กรณีทดสอบต่อไปนี้ล้มเหลว:
using NUnit.Framework;
using System;
using System.Collections.Generic;
namespace NUnitTestProject1
{
public class Tests
{
[TestCase(2, GCCollectionMode.Forced, true)]
public void TestWeakReferenceWithList(int generation, GCCollectionMode forced, bool blocking)
{
static List<int[]> CreateList()
{
return new List<int[]> { new int[2] };
}
WeakReference x;
{
var list = CreateList();
x = new WeakReference(list);
list = null;
}
Assert.IsTrue(x.IsAlive);
GC.Collect(generation, forced, blocking);
Assert.IsFalse(x.IsAlive);
}
}
}
หากเราดู IL เราสามารถเห็นว่าโมฆะถูกกำหนดให้กับตัวแปรโลคัล 1:
IL_0003: call class [System.Collections]System.Collections.Generic.List`1<int32[]> NUnitTestProject1.Tests::'<TestWeakReferenceWithList>g__CreateList|0_0'()
IL_0008: stloc.1
IL_0009: ldloc.1
IL_000a: newobj instance void [System.Runtime]System.WeakReference::.ctor(object)
IL_000f: stloc.0
IL_0010: ldnull
IL_0011: stloc.1
IL_0012: nop