Required introduction

Even the best performing component can be hindered by incorrect use. Below we provide practical recommendations on what should be used or avoided in application programming. Following the tips below you will be able to create applications with the same efficiency as this demo application. We also provide source codes for this example to show how easy it is.

  • It is supposed that programmers already know general principles of application design. Nevertheless, we shall provide some useful links for better understanding of .Net/Wpf environment and tools for writing managed code: Writing Faster Managed Code: Know What Things Cost

  • It is necessary to pay attention to grid characteristics in different working modes, to evaluate data volume to be displayed and to find a relevant chart for better understanding of grid use.

  • Use ThreadSafeBindingList<(Of <(<'T>)>)> instead of BindingList<(Of <(<'T>)>)> for the following reasons:

    1. BindingList<(Of <(<'T>)>)> has poor implementation when working with objects that implement INotifyPropertyChanged interface. Here you can find more information on BindingList<(Of <(<'T>)>)> performance issues. Use ThreadSafeBindingList<(Of <(<'T>)>)> instead, as it doesn’t allow BindingList<(Of <(<'T>)>)> to subscribe to events of objects that it contains.

    2. Another reason to use ThreadSafeBindingList<(Of <(<'T>)>)> instead of BindingList<(Of <(<'T>)>)> is related to implementation of MulticastDelegate used in events. With one subscriber, this delegate consumes minimum memory resources, but when there are two or more subscribers, it creates an internal subscriber collection that increases memory consumption dramatically. The grid always subscribes to objects that implement INotifyPropertyChanged interface and BindingList<(Of <(<'T>)>)> is the second subscriber.

  • If BindingList<(Of <(<'T>)>)> is used together with objects implementing INotifyPropertyChanged interface, it is better to avoid firing notifications with non-existent data object properties. It is mainly required because when a binding list receives such notification it checks whether the firing object has the specified property. If the firing object doesn’t have such property, the BindingList<(Of <(<'T>)>)> generates IBindingList..::..ListChanged event specifying ListChangedType.Reset reason and forcing the grid to rebuild all its contents.

  • Avoid using INotifyPropertyChanged interface, unless it is required. If the object doesn’t change at run-time, it is better not to use this interface to save memory and CPU resources.

  • Add data to the end of collection (List<(Of <(<'T>)>)>, BindingList<(Of <(<'T>)>)>, ThreadSafeBindingList<(Of <(<'T>)>)> etc). Otherwise, internal implementation of indexed collections will move and re-index all data starting from the newly added index.

  • Avoid adding data to a binding list with more than 10 000 items when the grid is sorted. Stop sorting to improve performance. However, if you need to add large amounts of data in real-time to the beginning of the grid, it's better to use the following:

    C# Copy imageCopy
    grid.Sorting.Enabled = false;
    grid.ItemsSource = _datasource_;
    
    //Add a single object to the beginning of the grid
    grid.Nodes.Insert(0, _new_object_);
    
    //Add a single object to the beginning of the binding list
    IBindingList bl = ...;
    bl.Insert(0, _new_object_);
  • Although the grid is thread-safe with INotifyPropertyChanged and IBindingList interfaces, use the GUI thread to work with it. This is mainly important for adding large data volumes. Some optimization algorithms work better when there is no need to switch between threads. Please also note that BindingList<(Of <(<'T>)>)> is not thread-safe.

  • Have only one message loop per application. Don't create grids in different threads.

    Let us assume that the computer resources are not limitless. Theoretically the maximum performance can be obtained if the number of threads equals to the number of processor cores. While a lot of threads are creating, they aren’t working in parallel. In this case cores allocate time slices to threads based on their priority and consistently perform context switches (context switch is relatively expansive operation). Note, maximum time slice is about 10-15 msec.

    From our point of view the application should have only one thread. Of course, business logic can work in any thread, but the graphical thread should be only one in the application.

  • Use objects of arbitrary classes instead of collections of values of string[] type, etc. It is more convenient and objects of arbitrary classes consume less memory than string collections.

  • Don’t format values directly in data objects, i.e. if an object property is to return date, this field should return DateTime object type instead of string type. Use data formatting if needed. If properties return strings, they can be compared incorrectly during data sorting. Besides, comparing two strings requires more CPU resources than comparing two DateTime objects.

    C# Copy imageCopy
    class DateExample
    {
        ...
    
        //Bad idea
        public string FormattedDate
        {
            get { return string.Format("{0:yyyy-MM-dd}", _date); }
        }
    
        //Good idea: the property should return non-formatted value
        [Converter("yyyy-MM-dd")]
        public DateTime Date
        {
            get { return _date; }
        }
    }
  • Use the event-driven model, if possible. On global scale it is more efficient than searching for rows in one or more grids with dynamically changing data.

  • Share the same business objects in different grids. Use declarative binding. This simplifies the application code and significantly reduces memory consumption.

  • Avoid using unnecessary wrappers. Use declarative binding instead of creating intermediate classes.

  • Use statically declared EventArgs for data fields to avoid creating numerous short-lived objects when sending notifications via INotifyPropertyChanged interface.

    C# Copy imageCopy
    class MyDataObject : INotifyPropertyChanged
    {
        private static readonly PropertyChangedEventArgs Value1Args = new PropertyChangedEventArgs("Value1");
    
        private string _value1;
    
        public string Value1
        {
            get { return _value1; }
            set
            {
                if (_value1 != value)
                {
                    _value1 = value;
    
                    //Bad idea: short-lived object is created
                    FirePropertyChanged("Value1");
    
                    //Good idea: notify with an immutable static object
                    FirePropertyChanged(Value1Args);
                }
            }
        }
    
        private void FirePropertyChanged(string fieldName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(fieldName));
            }            
        }
    
        private void FirePropertyChanged(PropertyChangedEventArgs args)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, args);
            }
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
    }
  • Work with business logic objects directly instead of calling Cell.Value/Row.DataAccessor["fieldId"].Value

    TypeDescriptor / PropertyDescriptor are used to display values in cells when working with arbitrary data types. The following code demonstrates value extraction from data objects.

    C# Copy imageCopy
    MyDataObject dataObject = ...;
    dataObject.Value1 = "some value";
    
    PropertyDescriptor property = TypeDescriptor.GetProperties(dataObject)["Value1"];
    object value = property.GetValue(dataObject);

    This working principle is the same for all grids of all vendors. The main issue is that PropertyDescriptor.GetValue(Object) method uses reflection. Wpf GridControl has an optimization that enables it to receive notifications from INotifyPropertyChanged / IBindingList without calling data object getters. Values are required only at the time of painting and only for visible cells. If the grid has a lot of rows, most of them are invisible and this approach significantly saves CPU resources. It is worth considering in application development. As an example, let’s review subscription to GridControl.RowUpdated event:

    C# Copy imageCopy
    grid.RowUpdated += (sender, e) =>
    {
        //This callback is called each time when a notification from INotifyPropertyChanged received.
    
        //Using of custom object will reduce CPU consumption
        MyDataObject myObject = (MyDataObject) e.Row.DataObject;
        decimal lastPrice = myObject.LastPrice;
    
        //The following call will use the reflection. Try to avoid.
        lastPrice = (decimal) e.DataField.Value;
    };
  • While construction of the hierarchy is preferable to collapse root rows:

    C# Copy imageCopy
    grid.RowAdded += delegate(object sender, GridRowEventArgs e)
    {
        e.Row.Expanded = false;
    };
  • Try avoiding reflection.

  • Be very careful about creating temporary objects as it may result in memory pressure and make garbage collector collect unused objects more frequently. Please note that when garbage is collected, all application threads are temporarily stopped.

  • Don’t use finalizers in objects unless necessary.

We hope that these simple recommendations will help you create efficient and high-performing applications.