Indexers breaking data binding

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.

5 Comments on Indexers breaking data binding

  1. Perhaps you should post a suggestion to modify the default behaviour to feedback center.

    Like

  2. Oliver,Thanks for this tip. I’ve wasted countless hours on this problem, due to 1) the poor MS documentation on this process and 2) the totally useless/missing debugging functionality for design-time code.I wish MS dev’s would get it through their collective brains that it is *not* ok to do things "auto-magically" without explaining *somehere* what’s really happening behind the scenes (and hopefully, providing a way to debug!) This sort of lazy/evil framework implementation looks great in the demos but we developers get screwed when we try to go beyond the basics.It turns out I had a property in my collection’s element class (reasonably) named, "Item". I simply renamed it and now the design view finally works.

    Like

  3. Tom, good to hear you figured it out 🙂

    Like

  4. Ugh. This is one of those problems I’ve come across before where you just end up bashing your head against the wall for hours trying to make it work. And then you spend hours trying tons of different ideas until you finally find the one that lets it compile and run. At which point you sit and look at the mangled code in disbelief at the solution before you finally go through and clean it all back up again.I’ve been there so many times!- Gary Webberhttp://www.badcreditremortgage.net/

    Like

  5. Thanks for sharing such a great post.

    Like

Leave a Comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s