[Doh! I am an idiot.. I am rooting the object right there in the code..]
I have code that works as expected in Release, but fails in Debug.
I have a Dictionary that contains WeakReference
instances to other objects. In Release, the dictionary “loses” its values as expected, once they are not referenced and collection occurs. However, in Debug, it doesn’t seem to happen…
Even in debug, I do see other WeakReference
getting collected in Debug, but the ones in the dictionary are not…
The code below shows this. Even when I add multiple Collects and delays between them (Task.Delay(100)
), it still does not go away.
Any idea how to force the WRs to get nulled? I don’t mind too much, but I have a test that tests for this and it will fail in Debug.
Here’s the code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication5
{
class Program
{
static void Main(string[] args)
{
DoIt();
Console.ReadLine();
}
private static async void DoIt()
{
string key = "k1";
var dict = new WeakItemDictionary<string, string>();
var s = dict.GetOrAdd(key, k => String.Concat("sdsdsd", "sdsdsdsdsd"));
RunFullGCCollection();
var found = dict.GetItemOrDefault(key);
Console.WriteLine(found == null ? "Object got collected" : "Object is still alive");
}
private static void RunFullGCCollection()
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
}
/// <summary>
/// Creates a dictionary of weakly referenced object that will disapear when no longer in use.
/// Be careful when adding functions to the class - you need to take a bunch of scenarios into account.
/// See how GetOrAdd() works for more info.
/// </summary>
/// <typeparam name="K">Key of the dictionary</typeparam>
/// <typeparam name="V">Value type for the dictionary</typeparam>
public class WeakItemDictionary<K, V> where V : class
{
public const int CleanPassFrequency = 10;
private Dictionary<K, WeakReference<V>> _dictionary = new Dictionary<K, WeakReference<V>>();
private int _addCount = 0;
public V GetOrAdd(K key, Func<K, V> factory)
{
WeakReference<V> weakRef;
V value = null;
if (!_dictionary.TryGetValue(key, out weakRef))
{
value = factory(key);
weakRef = new WeakReference<V>(value);
_dictionary[key] = weakRef;
_addCount++;
}
// If the value is null, try to get it from the weak ref (to root it).
if (value == null)
{
value = weakRef.GetTargetOrDefault();
// If the value is still null, means the weak ref got cleaned. We need to recreate (again, rooted)
if (value == null)
{
value = factory(key);
weakRef.SetTarget(value);
_addCount++;
}
}
CleanIfNeeded();
return value;
}
public V GetItemOrDefault(K key)
{
WeakReference<V> weakRef;
V value = null;
if (_dictionary.TryGetValue(key, out weakRef))
{
value = weakRef.GetTargetOrDefault();
}
return value;
}
private void CleanIfNeeded()
{
Lazy<List<K>> keysToRemove = new Lazy<List<K>>(false);
foreach (var item in _dictionary)
{
if (item.Value.IsDead())
{
keysToRemove.Value.Add(item.Key);
}
}
if (keysToRemove.IsValueCreated)
{
foreach (var item in keysToRemove.Value)
{
_dictionary.Remove(item);
}
}
}
}
public static class Extensions
{
public static bool IsDead<T>(this WeakReference<T> weak) where T : class
{
T t;
bool result = !weak.TryGetTarget(out t);
return result;
}
public static T GetTargetOrDefault<T>(this WeakReference<T> weak) where T : class
{
T t;
bool result = !weak.TryGetTarget(out t);
return t;
}
}
}