private static void InspectViewManager() { var finding = new DiagnosticFinding( "Data binding to non-observable collection OR uage of non-observable collection together with CollectionViewSource causes higher memory footpint." + "This is no real memory leak - the memory will be released after some 'Purge cylces' of the ViewManager. See http://referencesource.microsoft.com/PresentationFramework/Framework/MS/Internal/Data/ViewManager.cs.html", "If you need to free the memory as soon as it is no longer needed by your application consider converting this collection into one which implements INotifyCollectionChanged (e.g. ObservableCollection<>) - even if the collection is immutable."); var type = typeof(Binding).Module.GetType("MS.Internal.Data.ViewManager"); var viewManager = (IDictionary)type.GetProperty("Current", BindingFlags.Static | BindingFlags.NonPublic) .GetValue(null); if (viewManager == null) { return; } var inactiveViewTables = (IDictionary)type.GetField("_inactiveViewTables", BindingFlags.Instance | BindingFlags.NonPublic) .GetValue(viewManager); if (inactiveViewTables == null) { return; } if (inactiveViewTables.Count == 0) { return; } var entries = inactiveViewTables.OfType <DictionaryEntry>().ToList(); foreach (var entry in entries) { var viewTable = (IEnumerable)entry.Key; var entryWithViewRecord = (DictionaryEntry)viewTable.OfType <object>().First(); var collectionView = (ICollectionView)entryWithViewRecord.Value.GetType() .GetProperty("View", BindingFlags.Instance | BindingFlags.NonPublic) .GetValue(entryWithViewRecord.Value); var sourceCollection = collectionView.SourceCollection; if (collectionView.IsEmpty) { continue; } finding.AddLocation("CollectionType={0}, Count={1}", collectionView.SourceCollection.GetType().FullName, collectionView.SourceCollection.OfType <object>().Count()); } if (finding.Locations.Any()) { Findings.Add(finding); } }
// resolution: RemoveValueChanged() private static void InspectDPCustomTypeDescriptor() { var finding = new DiagnosticFinding( "Observation of DependencyProperty using DependencyPropertyDescriptor.AddValueChanged() causes memory leak", "Call DependencyPropertyDescriptor.RemoveValueChanged() to remove the event handler."); var type = typeof(DependencyObject).Module.GetType("MS.Internal.ComponentModel.DPCustomTypeDescriptor"); var propertyMap = (IDictionary)type.GetField("_propertyMap", BindingFlags.Static | BindingFlags.NonPublic) .GetValue(null); if (propertyMap == null) { return; } foreach (DictionaryEntry entry in propertyMap) { var dependencyObjectPropertyDescriptor = entry.Value; if (dependencyObjectPropertyDescriptor == null) { continue; } var trackers = GetTrackersFieldFromDependencyObjectPropertyDescriptor(dependencyObjectPropertyDescriptor); if (trackers == null) { continue; } foreach (DictionaryEntry trackerEntry in trackers) { var tracker = trackerEntry.Value; var changedHandler = (EventHandler)tracker.GetType() .GetField("Changed", BindingFlags.Instance | BindingFlags.NonPublic) .GetValue(tracker); if (changedHandler == null) { continue; } finding.AddLocation("ObservedType={0}, ObservedProperty={1}, HandlerTarget={2}, HandlerName={3}", tracker.GetType().GetField("_object", BindingFlags.Instance | BindingFlags.NonPublic) .GetValue(tracker).GetType().FullName, ((DependencyProperty)tracker.GetType().GetField("_property", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(tracker)).Name, changedHandler.Target.GetType().FullName, changedHandler.Method.Name); } } if (finding.Locations.Any()) { Findings.Add(finding); } }
// http://code.logos.com/blog/2008/10/detecting_bindings_that_should_be_onetime.html // resolution: OneTime, INotifyPropertyChanged private static void InspectReflectTypeDescriptionProvider() { var finding = new DiagnosticFinding( "Data binding to non-observable property causes memory leak", "Choose one of the following options:" + Environment.NewLine + " a) convert to DependencyProperty" + Environment.NewLine + " b) implement INotifyPropertyChanged in owning type" + Environment.NewLine + " c) bind with BindingMode=OneTime"); var type = typeof(PropertyDescriptor).Module.GetType("System.ComponentModel.ReflectTypeDescriptionProvider"); var propertyCache = (Hashtable)type .GetField("_propertyCache", BindingFlags.Static | BindingFlags.NonPublic) .GetValue(null); if (propertyCache == null) { return; } // try to make a copy of the hashtable as quickly as possible (this object can be accessed by other threads) var entries = new DictionaryEntry[propertyCache.Count]; propertyCache.CopyTo(entries, 0); var valueChangedHandlersFieldInfo = typeof(PropertyDescriptor) .GetField("valueChangedHandlers", BindingFlags.Instance | BindingFlags.NonPublic); foreach (var entry in entries) { var propertyDescriptors = (PropertyDescriptor[])entry.Value; if (propertyDescriptors == null) { continue; } foreach (var propertyDescriptor in propertyDescriptors) { var valueChangedHandlers = (Hashtable)valueChangedHandlersFieldInfo.GetValue(propertyDescriptor); if (valueChangedHandlers == null || valueChangedHandlers.Count == 0) { continue; } finding.AddLocation("ObservedType={0}, ObservedProperty={1}, HandlerCount={2}", entry.Key, propertyDescriptor.Name, valueChangedHandlers.Count); } } if (finding.Locations.Any()) { Findings.Add(finding); } }