Determining leaked objects
A while back I noticed that Pochet.NET was suffering from a memory leak, probably caused by some event handlers that were not properly removed. Although I had a hunch about what objects were leaking, I was not quite sure. That is why I decided to create the MemoryLeakDetector helper class. The sole purpose of this class is to keep track of how many instances of a specific type are active (i.e. not garbage collected).
This class can be used in the following ways: you can use the MemoryLeakDetector as the base class, like this:
public class MyClass : MemoryLeakDetector { ... }
or you can use an instance of the MemoryLeakDetector as an instance variable and initialize this variable in the constructor:
public class MyClass { private MemoryLeakDetector detector; public MyClass() { this.detector = new MemoryLeakDetector(this); ... } ... }
You can now use the following code to determine the number of active instances of all tracked objects:
foreach (KeyValuePair<Type, long> instanceCount in MemoryLeakDetector.InstanceCounts) { Debug.WriteLine("{0} {1}", instanceCount.Key.Name, instanceCount.Value); }
The number of active instance of an specific type can be found by
Debug.WriteLine(MemoryLeakDetector.GetInstanceCount(typeof(MyClass)));
This solution worked quite well for me, but there was one thing I noticed: no matter how many times I forced the garbage collector to recollect the garbage, it seemed that objects that are removed from the visual tree within Silverlight cannot be garbage collected immediately. It seems that some other thread has to do some housecleaning before those objects can be collected. So during debugging I had to wait some time to verify that all objects were garbage collected.
Here is the code for the class MemoryLeakDetector
namespace Pochet.Core.Helpers { using System; using System.Collections.Generic; using System.Threading; public class MemoryLeakDetector { #region Static private static object _lockInstanceCount = new object(); private static Dictionary<Type, long> _instanceCounts = new Dictionary<Type, long>(); private static void AddInstance(Type type) { lock (_lockInstanceCount) { long count = 0; _instanceCounts.TryGetValue(type, out count); _instanceCounts[type] = ++count; } } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "By design")] public static IEnumerable<KeyValuePair<Type, long>> InstanceCounts { get { Cleanup(); List<KeyValuePair<Type, long>> result = new List<KeyValuePair<Type, long>>(); lock (_lockInstanceCount) { foreach (KeyValuePair<Type, long> item in _instanceCounts) { result.Add(new KeyValuePair<Type, long>(item.Key, item.Value)); } } return result; } } public static long GetInstanceCount(Type type) { Cleanup(); lock (_lockInstanceCount) { long result; if (!_instanceCounts.TryGetValue(type, out result)) { return 0; } return result; } } private static void RemoveInstance(Type type) { lock (_lockInstanceCount) { long count = 0; if (_instanceCounts.TryGetValue(type, out count)) { if (--count <= 0) { _instanceCounts.Remove(type); } else { _instanceCounts[type] = count; } } } } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.GC.Collect", Justification = "Because accurate figures for memory usage are needed, the call to GC.Collect() is needed")] private static void Cleanup() { Thread.Sleep(1); GC.WaitForPendingFinalizers(); Thread.Sleep(1); GC.Collect(); Thread.Sleep(1); GC.WaitForPendingFinalizers(); Thread.Sleep(1); GC.Collect(); } #endregion #region Instance private Type _type; public MemoryLeakDetector() { this.Init(this.GetType()); } public MemoryLeakDetector(Type type) { this.Init(type); } ~MemoryLeakDetector() { RemoveInstance(this._type); } public long GetInstanceCount() { return GetInstanceCount(this._type); } private void Init(Type type) { if (type == null) { throw new ArgumentNullException("type"); } this._type = type; AddInstance(this._type); } #endregion } }

Sergio said:
Aug 06, 10 at 1:01 pmGood idea! But is there any elegant way to find out who is keeping elements which are not collected?
Emiel said:
Aug 10, 10 at 10:16 amI don’t really think so. The only way I can think of is to overload the assignment operator which is as far as I know prohibited in C#.
If you are really interested, probably you could use the Microsoft debugging tools such as Windbg, the Debug Diagnostic Tool and the like.