Search code examples
c#.netcachinggarbage-collectionobserver-pattern

How to get notified if a object is garbage collected?


Short version:

For a cache class I need to get notified if an object is garbage collected (to remove the according entries from my cache). What is the best way to do so? Sending an event from the destructor?

Long version:

I am writing a cacher/memoizer for functions that take one huge parameter-tree object and many small value type parameters, e.g.,

double myFunc(HugeParTree parTree, int dynPar1, double dynPar2)

I want to cache these functions in the following way:

  • Cache results for the tuples (parTree.GUID, dynPar1, dynPar2, ...)
  • Whenever parTree changes, which seldomly happens, all according cache entries are deleted (via Observer pattern). (parTree.Equals() is just too expensive; it compares 100+ value types).

Code looks like this right now (for one value parameter):

public class CachedFunction1ObsPar1Par<TRet, TObsPar1, TPar1>
        where TObsPar1 : IObservable, IProvideGUID
    {
        public delegate TRet ValueCalculator(TObsPar1 obsPar1, TPar1 par1);

        public CachedFunction1ObsPar1Par(ValueCalculator calc)
        {
            _calc = calc;
        }


        #region members

        private ValueCalculator _calc;

        private Dictionary<Guid, Dictionary<TPar1, TRet>> _cache = 
            new Dictionary<Guid, Dictionary<TPar1,TRet>>();

        #endregion


        public TRet value(TObsPar1 obsPar1, TPar1 par1)
        {
            TRet result;
            bool cacheHit = checkCache(obsPar1, par1, out result);

            if (cacheHit)
            {
                Debug.Assert(result.Equals(_calc(obsPar1, par1)));
                return result;
            }
            else
            {
                result = _calc(obsPar1, par1);
                _cache[obsPar1.GUID].Add(par1, result);
                return result;
            }
        }

        private bool checkCache(TObsPar1 obsPar1, TPar1 par1, out TRet result)
        {
            if (!_cache.ContainsKey(obsPar1.GUID))
            {
                _cache.Add(obsPar1.GUID, new Dictionary<TPar1, TRet>());
                obsPar1._changed += this.invalidateCache;
            }

            Dictionary<TPar1, TRet> guidCache = _cache[obsPar1.GUID];

            bool success = guidCache.TryGetValue(par1, out result);

            return success;
        }

        private void invalidateCache(object sender)
        {
            TObsPar1 obsPar = (TObsPar1)sender;

            _cache.Remove(obsPar.GUID);

            obsPar._changed -= this.invalidateCache;
        }
    }

I haven't tested this yet, as I still have the problem that cache entries never get removed after the according parTree is not used any more. I'd love a synchronous solution without repeated "scans" for very old cache entries.


Solution

  • For a cache class I need to get notified if an object is garbage collected (to remove the according entries from my cache). What is the best way to do so? Sending an event from the destructor?

    If your cache holds normal (strong) references the items will never be collected.

    If your cache holds WeakReferences you do not have to remove anything.