Partager via


WPF: Choosing the most appropriate collection for your data

A few months ago now, I was working with a customer where we were trying to performance tune their WPF 3.x application. One of the larger bottlenecks we found in the application was around the way lists of data were being held. It took much poking around and profiling of the application before we realised that we could improve the performance by replacing unnecessary ObservableCollections with Lists.

In this post I am going to attempt to show some of the variations in performance you will get when using an ObservableCollection<T> versus a List<T>. The tests I have done are by no means scientific, but they should hopefully highlight the need to consider this area in more detail when developing your application, or revisiting performance in retrospect.

I have put together a very simple test application which you can download at the end of this article. The application tackles the following three scenarios:

· Adding and modifying items in a List.

· Adding and modifying items in an ObservableCollection

· Adding and modifying items in an ObservableCollection that is bound to a ListBox in the UI.

The number of items to be added in each case is an arbitrary 100,000 Customer items, where the Customer type looks something like this:

    public class Customer : INotifyPropertyChanged

    {

        private string name;

        public string Name

        {

            get { return name; }

            set

            {

                name = value;

                OnPropertyChanged("Name");

            }

        }

        private int age;

        public int Age

        {

            get { return age; }

            set

            {

                age = value;

                OnPropertyChanged("Age");

            }

        }

        private Address officeLocation;

        public Address OfficeLocation

        {

            get { return officeLocation; }

            set

            {

                officeLocation = value;

                OnPropertyChanged("OfficeLocation");

            }

        }  

     // Remaining code removed for brevity

Below you can see a table where the timing information is given for each scenario when adding 100,000 records:

Time (ms) adding 100,000 records

Bound to a UI element?

List<Customer>

~ 29

No

ObservableCollection<Customer>

~ 109

No

ObservableCollection<Customer>

~ 1334

Yes

 

It may not be of great surprise to see that when bound to the UI the performance is several times slower than when it is not. Slightly more surprising though is that the ObservableCollection is a number of times slower than a List when there is no UI involved.

It is true to say that these numbers are all pretty small, and the variations could be considered negligible. Whilst this will be true for many simple forms-based applications that are binding to a small set of properties, it does become of greater concern when you are binding to large numbers of properties that perhaps contain hierarchical information. Furthermore, if you have lots of controls binding to the model, and that data frequently changes, you may also run into this bottleneck.

In the simple test application attached, you will find that despite changing properties frequently, the performance isn’t duly impacted and memory/CPU pressures remain low. In the real-world application however, we were hitting the performance bottlenecks. This really does emphasise that in many cases you may not encounter a problem, but if you do have a more complex scenario, you should consider the points in the following conclusion.

Conclusion

You can optimise the performance of your collections in a number of ways:

1. Ideally only use an ObservableCollection<T> if you need to expose change notifications to the UI.

2. If you need to work off the same set of data, and only need to update the UI some of the time, consider the following:

a. Work with a List<T> when performing your initial data operations, and convert to an ObservableCollection<T> when you are ready to expose your data to the UI. This approach won’t be suitable for most cases as it does not offer a lot of flexibility after the initial data has been populated.

b. In your Property Setters, figure out whether you need to update the UI for a particular update. If you don’t, then consider whether or not you need to fire the PropertyChanged event. For example, you might have some code that looks like this:

 if (UI_MODE)

OnPropertyChanged("Age");

                                Where UI_MODE can be toggled depending on how the data is being consumed.

c. When adding or removing items from an ObservableCollection<T>, a CollectionChanged event will fire for each item. This can have quite a performance impact and you may want to batch up UI notifications. Whereas you can code around this, there is a new type in .NET 4.0 that will support batching and therefore improve performance in many scenarios. See BulkObservableCollection<T> (https://msdn.microsoft.com/en-us/library/dd867973(VS.100).aspx) for further details.

As we were dealing with .NET 3.x, and because we wanted more fine grained control over when the events would fire, the solution of choice for the real-world application was 2b.

If you have hit any problems in this area and have a more readily reproducible sample of the behaviour, it would be great to hear from you.

 

WPF Performance.zip