У меня есть библиотека, которая обрабатывает объекты (которые оказываются неизменными). Часто он обрабатывает одни и те же объекты снова и снова, но у него нет возможности узнать, поэтому он выполняет множество повторяющихся действий.
Хорошей аналогией может быть то, что я визуализирую интерфейс. Моя функция рендеринга возвращает кадр, но 99% работы, которая ушла на рендеринг этого кадра, используется и выбрасывается. Большая часть этой работы может быть использована повторно, если в следующий раз, когда меня попросят визуализировать фрейм, содержащий этот элемент управления, я пойму, что это тот же элемент управления (только, возможно, с немного другим состоянием).
Если бы это была реальная жизнь, и я имел бы дело с людьми, я бы дал каждому «удостоверение личности», чтобы вернуть мне его в следующий раз, когда они придут, чтобы я мог найти их в своей базе данных, если я когда-нибудь их снова увижу. К сожалению, в большинстве языков программирования (включая C#) вы не можете просто «дополнить» произвольные объекты пользовательскими данными, чтобы их было легче идентифицировать позже.
Я подумал о хранении в кеше объектов, которые видел раньше:
Dictionary<Object, ReallyExpensiveInformation> cache = new ...;
Или, если нас беспокоят объекты, которые неправильно переопределяют "Равно":
Dictionary<CachedObject, ReallyExpensiveInformation> cache = new ...;
// Where CachedObject is defined as:
private class CachedObject : IEquatable<CachedObject> {
Object Value { get; set; }
// Get the native hash code that is based on the object reference
override int GetHashCode() => RuntimeHelpers.GetHashCode(Value);
// Ensure each new reference is processed, even if they would claim to be equivalent
override bool Equals(CachedObject other) => ReferenceEquals(Value, other.Value);
}
На мой взгляд, оба этих подхода страдают от одной и той же основной проблемы, заключающейся в том, что они создают утечка памяти, удерживая объекты, которые в противном случае могли бы быть завершены.
Я сказал, что сталкиваюсь с одними и теми же объектами снова и снова, но сталкиваюсь с гораздо большим количеством, которых больше никогда не увижу. У меня есть много места для хранения ReallyExpensiveInformation, потому что конечный результат невелик, но Objects может быть большим, и я не знаю, какие из них держит вызывающий абонент, а какие отказываются от сбора мусора. Вызывающий может хранить или не хранить кучу ссылок на эти объекты, но если они решат, что с ними покончено, я не хочу, чтобы мой кеш был тем, что удерживало их от сбора мусора.
Мне почти жаль, что не было способа перехватить «счетчик ссылок» объекта и сбросить его, когда я останусь последним.
В любом случае, моей следующей мыслью было бы создать свою собственную фазу "GarbageCollection". Улучшите мой словарь, указав время последнего доступа, и удалите все записи, которые давно не использовались повторно. Однако это сложная проблема, которую нужно решить. Мне интересно, не забыл ли я придумать более простое решение.
Надеюсь, ясно, что я не контролирую обрабатываемые объекты, иначе я бы улучшил их объектную модель.
@Eser Однажды я вспомню, что он существует ... надеюсь, что это тот день, когда он мне понадобится. знак равно
WeakReference выглядит невероятно полезным. Дай мне посмотреть, что я могу сказать об этом.
@Eser Я попытался разработать решение на основе этого класса, если у вас есть какие-либо дополнительные предложения или ошибки. Спасибо!





Итак, @Eser только что подключил меня к System.WeakReference. Посмотрим, как это будет выглядеть:
private class CachedReference : IEquatable<CachedObject> {
private readonly int _hash;
public WeakReference Value { get; }
public CachedReference (Object obj) {
Value = new WeakReference(obj);
_hash = RuntimeHelpers.GetHashCode(Value);
}
override int GetHashCode() => _hash;
// If our value is garbage collected, we'll stop matching anything
override bool Equals(CachedObject other) =>
Value != null && ReferenceEquals(Value, other.Value);
}
ConcurrentDictionary<CachedReference, ReallyExpensiveInformation> cache = new ...;
// And we use it like this
public ReallyExpensiveInformation GetOrCompute(Object obj) =>
return cache.GetOrAdd(new CachedReference(obj), key => ComputeExpensiveInfo(key.Value));
Похоже, нам все еще нужен какой-то процесс, чтобы «собрать мусор» словарные статьи, которые остались висящими:
Возможно, что-то вроде этого?
Timer garbageCollection = new Timer { AutoReset = true; Interval = 1000 };
Timer.Elapsed += () => cache.Keys.Where(k => k.Value == null)
.ToList().ForAll(k => cache.TryRemove(k, out _));
Это довольно грубо, и мне не нравится мысль о том, что периодические удаления O(N) мешают работе кеша. (например, "замораживание" или замедление доступа во время очистки или замены).
Определенно лучше, чем мы были, но мне интересно, есть ли что-нибудь более элегантное, что можно сделать с уборкой.
Какую информацию хотите сохранить? Я почти хочу сказать: разорвите объект с помощью отражения, получите то, что вам нужно, а затем используйте HashCode в качестве ключа.