WeakReference, .Net Framework ve .Net Core arasında farklı davranır
Aşağıdaki kodu göz önünde bulundurun:
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 üzerinde bir sürüm veya hata ayıklama yapısıyla, bu kod yazdırılır
False
. - NET Core 3.1 üzerinde bir sürüm veya hata ayıklama yapısıyla, bu kod yazdırılır
True
.
Davranıştaki bu farklılığa ne sebep oluyor? (Bazı birim testlerimizin başarısız olmasına neden oluyor.)
Not: makeList()
.Net Core sürümünün .Net Framework sürümü ile aynı şekilde çalışmasını sağlamak amacıyla liste başlatmayı başlattım ve inlining'i kapattım, ancak boşuna.
[DÜZENLE] Hans'ın belirttiği gibi, bir döngü eklemek sorunu düzeltir.
Aşağıdaki kod olacaktır yazdırmak 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);
Ancak bu basılacak 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);
Bu gelmiştir var garip Değişimi şey bir tür olması ...
Yanıtlar
Bir şey sırf izin anlamına gelmez toplanacak o oluyor yükümlü kısa sürede olduğu gibi toplanacak. Dil, GC'nin yerel bir değişkenin bir daha asla okunmayacağını belirlemesine izin verildiğini ve dolayısıyla onu bir kök olarak kabul etmediğini belirtirken, bu, yerel bir değişkenin içeriğinin ondan son okuduktan hemen sonra toplanmasına güvenebileceğiniz anlamına gelmez .
Bu, çalışma zamanında tanımlanan davranış arasında bir değişiklik değildir; bu, her iki çalışma zamanında da tanımlanmamış bir davranıştır , bu nedenle aralarındaki farklılıklar tamamen kabul edilebilir.
Liste değişkenini kaldırdığımda serbest bırakılacak referansı aldım:
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);
}
}
}
Aşağıdaki test durumu başarısız olur:
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'ye bakarsak, null değerinin yerel değişken 1'e atandığını görebiliriz:
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