Alright, consider the following situation: There’s a data class with two properties, Index and Content. There’s a specialised collection typed to contain instances of the data class. Something like this:

public class DataClass {
  public DataClass(string index, string content) {
    this.index = index;
    this.content = content;
  }

  string index;
  public string Index { get { return index; } }

  string content;
  public string Content { get { return content; } }
}

public class MyCollection : List<DataClass> {
}

Of course, if you are using .NET 1, you’ll use a collection derived from CollectionBase instead. As far as I could see, the result would be the same in .NET 1, but I didn’t test it. Maybe someone else could confirm it? Now, in a little test program, you create some instances of DataClass and stick them in a MyCollection instance. You bind the collection to a DataGrid or a DataGridView, run the app and everything’s fine. Okay. Now you want to introduce an additional indexer into the collection that will find a specific item by its Index, returning the Content:

public class MyCollection : List<DataClass> {
  public string this[string index] {
    get {
      foreach (DataClass dc in this)
        if (dc.Index == index)
          return dc.Content;
        return null;
    }
  }
}

Run the same test app again and you’ll get a wonderful exception saying “Property accessor ‘Length’ on object ‘DataClass’ threw the following exception:‘Object does not match target type.‘”

Now what’s happening here? Well, it’s really simple (isn’t it always?). The mechanism that’s used to find out all the properties that are needed to bind a specific collection is quite elaborate. Most of us have probably seen the problem where no properties at all seem to be found because we were binding a collection that didn’t hold any objects at the point where we bound it, just as an example of the complex mechanisms at work here. In this case, the reason is one I haven’t found documented anywhere: Under specific circumstances, the method GetItemProperties of the System.Windows.Forms.CurrencyManager is used to fetch a PropertyDescriptorCollection that can be used with a bound collection. For the sake of completeness, this method takes four steps to get hold of a suitable property descriptor collection:

  1. If the bound list is an Array type, the element type of the array is used with the TypeDescriptor to get a property descriptor collection.
  2. If the bound list implements ITypedList, the property descriptors are fetched from there.
  3. The third step is the interesting one, see below.
  4. If the collection contains at least one item, the TypeDescriptor is used to get the property descriptors for the first item in the collection.

Now, the third step is the most interesting one and also the one creating the problems here: Property descriptors are fetched for the collection itself. This list of descriptors is then checked for one that has the name Item (which is the name of any indexer properties, even if you normally don’t notice in C#) and a return type that’s not object. If such a descriptor is found, the TypeDescriptor is used to get a list of property descriptors for the return type of that property!

So if you have a look at the sample source code again, it becomes clear what’s happening here. That third step kicks in because the collection has an indexer property that doesn’t return object. The currency manager assumes that the return type of the indexer (did I mention, the method doesn’t even check if there’s more than one indexer) is the same as the type of the items in the collection. Although this may be right more often than not, that’s quite a stupid assumption, if you ask me… even more so if you’re not planning on documenting this behaviour anywhere.

Well, in my sample that return type is string, so the property descriptors for string are finally used to access properties of the objects in the collection. The one browseable property that string has is called Length, and that’s exactly what the problem is about in the exception message. Working around this problem is quite easy: Just implement ITypedList on the collection, with a GetItemProperties implementation like this:

public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors) {
  return TypeDescriptor.GetProperties(typeof(DataClass),
    new Attribute[] { new BrowsableAttribute(true) });
}

I found this problem in .NET 2, beta 1, and I checked the code of .NET 1.1, too. I haven’t actually tried the sample in .NET 1, though.