4
votes

I'm working on an application built using C# and WPF, which (badly) implements MVVM. The breakdown of work is something like:

  • View

    • Presentation, bitmaps, colors, etc.
    • Animations (if used)
    • Communicates with the ViewModel by being data bound to it
    • Communicates with the ViewModel by raising commands on it
  • ViewModel

    • Exposes commands to be invoked by view(s)
    • Delegates data processing functionality to the model
    • Defines UI behavior
    • No direct (named) dependency on the view
    • Communicates with the Model by calling methods in the model
    • May be notified of changes in the model by subscribing to events exposed by the model
  • Model

    • Disk persistence, data analysis, etc
    • Everything else
    • No dependency on the ViewModel

Unfortunately, this has resulted in circular references, because view(s) need references to viewmodels to raise events and fire commands, viewmodels need references to views in order to update view state (typically this reference is by a viewmodel being WPF's DataContext), viewmodels need a reference to models to delegate work, and models often need to notify viewmodels of some external change in state.

So we have two circular reference problems, with the viewmodel sitting in the middle. As a result, this application is running into memory consumption problems because WPF entities are being created and associated with some piece of data in the model, and these entities are never cleaned up (until the program is terminated).

How is this supposed to be handled?

It seems like an ownership graph needs to be defined, such that one or more of these components is responsible for disconnecting event handlers when they are no longer relevant, so that things can be GC'd.

3

3 Answers

1
votes

Your question does not provide enough information about what gets instantiated and I doubt that it will be possible to solve this without digging through all the code.

First: This is a really bad MVVM as separation of layers and concerns is violated here.

I would suggest to fix this first. Your issue might be a poor understanding of XAML and instantiation at all (no offence)

Without touching the architecture you will need to profile your application to see what objects gets instantiated how many times (memory hotspots) It might also be interesting to see in which GC generation those objects are as this might indicate some serious (manual) memory management issue.

Event Handlers tend to create leaks. Therefore Weak reference patterns may be applied: http://msdn.microsoft.com/en-us/library/aa970850.aspx

A good MVVM has some sort of lifetime management that also servers as DI and IoC container through which complex lifetime scenarios should be handled

1
votes

Ok, I thought you've used all of them together directly

If you have not any cached view, then why you've high memory usage ?

You can not have memory problem in a MVVM application ( even non well architectured ones ) What static items do you have ?

Have you ever used this code to investigate the current situation of bindings of your wpf application ?

private static IList<BindingInfo> getReflectPropertyDescriptorInfo()
        {
            var results = new List<BindingInfo>();

            var reflectTypeDescriptionProvider = typeof(PropertyDescriptor).Module.GetType("System.ComponentModel.ReflectTypeDescriptionProvider");
            var propertyCacheField = reflectTypeDescriptionProvider.GetField("_propertyCache",
                BindingFlags.Static | BindingFlags.NonPublic);
            if (propertyCacheField == null)
                throw new NullReferenceException("`ReflectTypeDescriptionProvider._propertyCache` not found");

            var propertyCacheItems = propertyCacheField.GetValue(null) as Hashtable;
            if (propertyCacheItems == null)
                return results;

            var valueChangedHandlersField = typeof(PropertyDescriptor).GetField("valueChangedHandlers",
                BindingFlags.Instance | BindingFlags.NonPublic);

            if (valueChangedHandlersField == null)
                return results;

            foreach (DictionaryEntry entry in propertyCacheItems)
            {
                var properties = entry.Value as PropertyDescriptor[];
                if (properties == null)
                    continue;

                foreach (var property in properties)
                {
                    var valueChangedHandlers = valueChangedHandlersField.GetValue(property) as Hashtable;
                    if (valueChangedHandlers != null && valueChangedHandlers.Count != 0)
                        results.Add(new BindingInfo
                            {
                                TypeName = entry.Key.ToString(),
                                PropertyName = property.Name,
                                HandlerCount = valueChangedHandlers.Count
                            });
                }
            }

            return results;
        }

With this code you can find out what binding are in memory ?

0
votes

" viewmodels need references to views in order to update view state (typically this reference is by a viewmodel being WPF's DataContext), " - At this point there is no dependency as View does not know what is in the DataContext and ViewModel does not really know anything about View, so it is not deffinitly Devependency of the in ViewModel of the View.

You got dependency when you need to assign ViewModel to DataContext, and View is dependent of ViewModel (it does not need to be as IViewModel can be used instead of ViewModel)

Even with the ViewModel first approach you don't have actually View, you have IView contract that is implemented by View.

class ContentViewModel{


 IView view;

 public ContentViewModel(IContentAView view)
       {
            View = view;
            View.ViewModel = this;

If you using container as Unity and View Model or View is instanciated every time but old instance is not GC because of some reference( maybe event handlers) you will have memory leak. You need to profile memory and find out when new instance is created and find references to the old instance by using some tool such as RedGate memory profiler of Microsoft WinDbg.