In a comment about my post Simulating object properties with ITypedList and custom PropertyDescriptors, John Eyles mentioned the fact that the TypeDescriptor.GetProperties method doesn’t return the type’s properties in any fixed order. Therefore, when my own ITypedList implementation takes at least parts of its properties from a standard set that was fetched via the TypeDescriptor, additional steps have to be taken if the properties are supposed to appear in a particular order, like in a grid or similar. Wouldn’t it be nice if the properties could be ordered from the beginning? Actually, I had already implemented this in the past, so I thought I’d just put up another post to show how it can be done.

Parts of the solution

The final solution is comprised of several parts, which I’ll explain one after the other. In short, there are an attribute to decorate properties with the desired sort order (and, as an add-on, whether to show the property at all), a wrapper class for a property descriptor that makes it sortable based on a sort index and, last and most important, a helper class that uses the attributes and the sortable wrapper to return pre-sorted property collections. The helper class also implements a simple cache, so this doesn’t become too inefficient if used often.

The attribute

The attribute allows the user to decorate properties of data classes, giving information about the desired sort order and whether or not to make a property visible. Here’s the code for that:

[AttributeUsage(AttributeTargets.Property,
  AllowMultiple=false, Inherited=true)]
public class BindingInfoAttribute : Attribute {
  public BindingInfoAttribute() {
    this.visible = true;
  }

  public BindingInfoAttribute(bool visible) {
    this.visible = visible;
  }

  private bool visible;
  public bool Visible {
    get { return visible; }
    set { visible = value; }
  }

  private int sortIndex;
  public int SortIndex {
    get { return sortIndex; }
    set { sortIndex = value; }
  }
}

The property descriptor wrapper

To be able to sort a list of property descriptors according to the SortIndex given in the BindingInfoAttribute, we need this wrapper class. It stores a sort index and implements the IComparable interface to make use of it during sorting. Note that this class doesn’t derive from PropertyDescriptor, so maybe the name isn’t very fitting, but I wasn’t going to change that now.

public class SortablePropertyDescriptor : IComparable {
  public SortablePropertyDescriptor(PropertyDescriptor descriptor, int sortIndex) {
    this.descriptor = descriptor;
    this.sortIndex = sortIndex;
  }

  private PropertyDescriptor descriptor;
  public PropertyDescriptor Descriptor {
    get { return descriptor; }
  }

  private int sortIndex;
  public int SortIndex {
    get { return sortIndex; }
  }

  public int CompareTo(object obj) {
    SortablePropertyDescriptor other = obj as SortablePropertyDescriptor;
    if (other == null) throw new ArgumentException(
      "The given object is not of type SortablePropertyDescriptor.", "obj");
    return this.SortIndex - other.SortIndex;
  }
}

The helper class

The PropertyDescriptorCollectionHelper has the most important job, proving the GetPropertyCollection method. This method looks at all the properties of the given type, searching for binding information given by the BindingInfoAttribute. If such information is found, it’s used to define which properties to include in the result collection and also the sort order of the properties. Using the parameters doSort and skipCache, the caller can decide whether sorting should be skipped and whether to use the internal descriptor collection cache. The implementation uses generic collection types, but for .NET 1.1 the same thing should work just fine using ArrayLists in the places of the typed generic collections.

public class PropertyDescriptorCollectionHelper {
  public PropertyDescriptorCollectionHelper() {
    cache = new Dictionary<Type,PropertyDescriptorCollection>();
  }

  private static PropertyDescriptorCollectionHelper current;
  public static PropertyDescriptorCollectionHelper Current {
    get {
      if (current == null)
        current = new PropertyDescriptorCollectionHelper();
      return current;
    }
    set { current = value; }
  }

  private Dictionary<Type, PropertyDescriptorCollection> cache;

  public PropertyDescriptorCollection GetPropertyCollection(Type type,
    bool doSort) {
    return GetPropertyCollection(type, doSort, false);
  }

  public PropertyDescriptorCollection GetPropertyCollection(Type type) {
    return GetPropertyCollection(type, true, false);
  }

  public PropertyDescriptorCollection GetPropertyCollection(Type type,
    bool doSort, bool skipCache) {
    PropertyDescriptorCollection result = skipCache ? null :
      cache.ContainsKey(type) ? cache[type] : null;

    if (result == null) {
      PropertyDescriptorCollection origProperties =
        TypeDescriptor.GetProperties(type);
      List<PropertyDescriptor> resultList = doSort ?
        null : new List<PropertyDescriptor>( );
      List<SortablePropertyDescriptor> sortList = doSort ?
        new List<SortablePropertyDescriptor>() : null;

      foreach (PropertyDescriptor descriptor in origProperties) {
        BindingInfoAttribute bindingInfo = descriptor.Attributes[
          typeof(BindingInfoAttribute)] as BindingInfoAttribute;
        if (bindingInfo == null || bindingInfo.Visible) {
          if (doSort)
            sortList.Add(new SortablePropertyDescriptor(descriptor,
              bindingInfo != null ? bindingInfo.SortIndex : int.MaxValue));
          else
            resultList.Add(descriptor);
        }
      }

      if (doSort) {
        sortList.Sort();
        resultList = ReduceSortableList(sortList);
      }

      result = new PropertyDescriptorCollection(resultList.ToArray());

      cache[type] = result;
    }

    return result;
  }

  private List<PropertyDescriptor> ReduceSortableList(
    List<SortablePropertyDescriptor> sortList) {
    List<PropertyDescriptor> resultList = new List<PropertyDescriptor>();
    foreach (SortablePropertyDescriptor desc in sortList)
      resultList.Add(desc.Descriptor);
    return resultList;
  }
}

How do I use that?

Now, to actually use these classes with your own data objects, you need to decorate your properties with the BindingInfoAttribute, like this:

public class DataClass {
  private string stringProperty1;
  [BindingInfo(SortIndex=2)]
  public string StringProperty1 {
    get { return stringProperty1; }
    set { stringProperty1 = value; }
  }

  private int intProperty1;
  [BindingInfo(SortIndex=1)]
  public int IntProperty1 {
    get { return intProperty1; }
    set { intProperty1 = value; }
  }

  private double doubleProperty1;
  [BindingInfo(Visible=false)]
  public double DoubleProperty1 {
    get { return doubleProperty1; }
    set { doubleProperty1 = value; }
  }
}

To retrieve a sorted collection of property descriptors for DataClass, you only need one line:

PropertyDescriptorCollection properties =
  PropertyDescriptorCollectionHelper.Current.GetPropertyCollection(typeof(DataClass));

Have fun!