Search code examples
c#cachingmemory-leaksmetadata

The correct way to store some metadata associated with an object?


Firstly I'm afraid of the memory leak issue here. Because what I do is using some Dictionary to store the metadata with Key of the object itself. So I can lookup for metadata (which should be dynamic) by passing in the object.

Note that the object here does not have any GUID to help identify itself, the only way to identify it is by its instance. This kind of storing looks much like what in DependencyObject in WPF. We can declare DependencyProperty (attached property) to extend the DependencyObject (the metadata is contained in the attached property).

Here is what I have:

Dictionary<object, SomeMetaData> _metadataLookup = new Dictionary<object,SomeMetaData>();
//to store metadata
_metadataLookup[someObject] = someMetaData;
//to get metadata
SomeMetaData someMetaData;
_metadataLookup.TryGetValue(someObject, out someMetaData);

It would be fine if the someObject's lifetime is the same as the lifetime of the scope using the _metadataLookup. But what if the someObject (after stored in _metadataLookup) should be destroyed (logically become unused)? We have to manually remove it from _metadataLookup as well, otherwise there is a memory leak there.

I'm seeking for a better solution for this issue, unexpectedly forgetting to remove the objects manually or simply not knowing when the objects should be removed is a problem here.


Solution

  • For this purpose you can use ConditionalWeakTable

    public class Example
    {
       public static void Main()
       {
          var mc1 = new ManagedClass();
          var mc2 = new ManagedClass();
          var mc3 = new ManagedClass();
    
          var cwt = new ConditionalWeakTable<ManagedClass, ClassData>();
          cwt.Add(mc1, new ClassData());          
          cwt.Add(mc2, new ClassData());
          cwt.Add(mc3, new ClassData());
    
          var wr2 = new WeakReference(mc2);
          mc2 = null;
    
          GC.Collect();
    
          ClassData data = null; 
    
          if (wr2.Target == null)
              Console.WriteLine("No strong reference to mc2 exists.");   
          else if (cwt.TryGetValue(mc2, out data))
              Console.WriteLine("Data created at {0}", data.CreationTime);      
          else
              Console.WriteLine("mc2 not found in the table.");
       }
    }
    
    public class ManagedClass
    { 
    }
    
    public class ClassData
    {
       public DateTime CreationTime;
       public object Data;   
    
       public ClassData()
       {
          CreationTime = DateTime.Now;
          this.Data  = new object();     
       }
    }
    // The example displays the following output:
    //       No strong reference to mc2 exists.