Это моя версия параллельного слабого (значения) словаря:
public class WeakConcurrentDictionary<TKey, TValue> : IDictionary<TKey, TValue> where TValue : class
{
private readonly ConcurrentDictionary<TKey, WeakReference<TValue>> _internalDictionary =
new ConcurrentDictionary<TKey, WeakReference<TValue>>();
public TValue this[TKey key]
{
get
{
if (_internalDictionary.TryGetValue(key, out var weakReference) &&
weakReference.TryGetTarget(out var value))
return value;
return null;
}
set
{
_internalDictionary.TryAdd(key, new WeakReference<TValue>(value));
}
}
public ICollection<TKey> Keys => _internalDictionary.Keys;
public ICollection<TValue> Values => _internalDictionary.Values
.Select(_ => _.GetTarget())
.Where(_ => _ != null)
.ToList();
public int Count => _internalDictionary.Count;
public bool IsReadOnly => false;
public void Add(TKey key, TValue value)
{
Purge();
if (!_internalDictionary.TryAdd(key, new WeakReference<TValue>(value)))
{
throw new InvalidOperationException("Key already existing");
}
}
public void Add(KeyValuePair<TKey, TValue> item)
{
throw new NotSupportedException();
}
public void Clear()
{
_internalDictionary.Clear();
}
public bool Contains(KeyValuePair<TKey, TValue> item) => _internalDictionary.TryGetValue(item.Key, out var weakReference) &&
weakReference.GetTarget() == item.Value;
public bool ContainsKey(TKey key) => _internalDictionary.TryGetValue(key, out var weakReference) &&
weakReference.IsAlive();
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
Purge();
_internalDictionary
.Select(_ => new KeyValuePair<TKey, TValue>(_.Key, _.Value.GetTarget()))
.Where(_ => _.Value != null)
.ToList()
.CopyTo(array, arrayIndex);
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
Purge();
return _internalDictionary
.Select(_ => new KeyValuePair<TKey, TValue>(_.Key, _.Value.GetTarget()))
.Where(_ => _.Value != null)
.GetEnumerator();
}
public bool Remove(TKey key)
{
return _internalDictionary.TryRemove(key, out var weakReference);
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
throw new NotSupportedException();
}
public bool TryGetValue(TKey key, out TValue value)
{
value = null;
if (_internalDictionary.TryGetValue(key, out var weakReference))
{
value = weakReference.GetTarget();
}
return value != null;
}
IEnumerator IEnumerable.GetEnumerator()
{
Purge();
return GetEnumerator();
}
public void Purge()
{
foreach (var itemToRemove in _internalDictionary
.Select(_ => new KeyValuePair<TKey, TValue>(_.Key, _.Value.GetTarget()))
.Where(_ => _.Value == null))
{
_internalDictionary.TryRemove(itemToRemove.Key, out var weakReference);
}
}
}
public static class WeakReferenceExtensions
{
public static bool IsAlive<T>([NotNull] this WeakReference<T> weakReference) where T : class =>
weakReference.TryGetTarget(out var target);
public static T GetTarget<T>([NotNull] this WeakReference<T> weakReference, T defaultValue = default(T)) where T : class
{
if (!weakReference.TryGetTarget(out T target))
return defaultValue;
return target;
}
}
и тест, доказывающий, что ссылка на значение фактически отбрасывается:
[TestMethod]
public void TestWeakDictionary()
{
var weakDict = new WeakConcurrentDictionary<string, TestItem>();
{
var testItem = new TestItem();
weakDict.Add("testitem", testItem);
Assert.AreEqual(1, weakDict.Count);
Assert.AreSame(testItem, weakDict["testitem"]);
}
GC.Collect();
Assert.IsNull(weakDict["testitem"]);
weakDict.Purge();
Assert.AreEqual(0, weakDict.Count);
}
Некоторые заметки:
- Property Keys возвращает все ключи, даже те записи, значение которых было собрано, но Values всегда возвращает живые ненулевые объекты.
- этот [ключ] МОЖЕТ вернуть ноль
- Вы также можете вызвать Очистить, чтобы очистить записи, значения которых были собраны
- Тест работает при компиляции и выполнении в режиме Release