It seems the following code to unit test WeakReference
does not work correctly/reliably:
object realObject = new object();
WeakReference weakReference = new WeakReference(realObject);
Assert.NotNull(weakReference.Target);
realObject = null;
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Assert.Null(weakReference.Target);
The testing is run targeting both net461 and net5.0, for both DEBUG and RELEASE mode. The best result:
Targeting net461: the above code runs successfully, for both DEBUG and RELEASE mode.
Targeting net5.0: the above code fails for both DEBUG and RELEASE mode at first. After reading this post (Garbage collection behaviour difference between .NetFramework 4.8 and .Net 5) it works running in RELEASE mode by adding
<TieredCompilation>false</TieredCompilation>
to.csproj
.
So the problem narrows down to targeting net5.0 DEBUG mode.
I also came across the following posts but it seems they're not very helpful mostly because they're out-of-date:
Worth to mention the answer from @Eric Lippert for Why C# Garbage Collection behavior differs for Release and Debug executables? says: You absolutely cannot rely on the garbage collector having any particular behaviour whatsoever with respect to the lifetime of a local, which suggests testing a WeakReference cannot be done reliably.
Edit: More information about the context of why using a WeakReference
:
I'm developing a framework providing class Entity<T>
and Record<T>
similar to System.Data.DataRow
and System.Data.DataRowView
, with the most significance that the prior one is strongly typed. Entity is the model provides its own event to notify changes, Record<T>
is a viewmodel that wraps Entity<T>
to translate the change event into INotifyPropertyChanged
plus other things such as databinding, validations, etc. Entity<T>
should not aware the existence of Record<T>
. To avoid memory leak, Record<T>
should be GC reclaimed when only Entity is still referenced.
The example code for the event hookup:
IEntityListener
Implemented by Record<T>
class:
internal interface IEntityListener
{
void OnFieldUpdated(FieldUpdatedEventArgs e);
}
WeakEntityListner
to do the event hookup:
internal sealed class WeakEntityListener
{
private readonly WeakReference<IEntityListener> _weakListener;
private readonly Entity _entity;
internal WeakEntityListener(Entity entity, IEntityListener listener)
{
_entity = entity;
_weakListener = new (listener);
_entity.FieldUpdated += OnFieldUpdated;
}
private void OnFieldUpdated(object? sender, FieldUpdatedEventArgs e)
{
if (_weakListener.TryGetTarget(out var listener))
listener.OnFieldUpdated(e);
else
CleanUp();
}
private void CleanUp()
{
_entity.FieldUpdated -= OnFieldUpdated;
}
}
I would like to have method OnFieldUpdated
and CleanUp
fully covered by unit test.