C # grid DataSource Polymorphism

advertisements

I have a grid, and I'm setting the DataSource to a List<IListItem>. What I want is to have the list bind to the underlying type, and disply those properties, rather than the properties defined in IListItem. So:

public interface IListItem
{
    string Id;
    string Name;
}

public class User : IListItem
{
    string Id { get; set; };
    string Name { get; set; };
    string UserSpecificField { get; set; };
}

public class Location : IListItem
{
    string Id { get; set; };
    string Name { get; set; };
    string LocationSpecificField { get; set; };
}

How do I bind to a grid so that if my List<IListItem> contains users I will see the user-specific field? Edit: Note that any given list I want to bind to the Datagrid will be comprised of a single underlying type.


Data-binding to lists follows the following strategy:

  1. does the data-source implement IListSource? if so, goto 2 with the result of GetList()
  2. does the data-source implement IList? if not, throw an error; list expected
  3. does the data-source implement ITypedList? if so use this for metadata (exit)
  4. does the data-source have a non-object indexer, public Foo this[int index] (for some Foo)? if so, use typeof(Foo) for metadata
  5. is there anything in the list? if so, use the first item (list[0]) for metadata
  6. no metadata available

List<IListItem> falls into "4" above, since it has a typed indexer of type IListItem - and so it will get the metadata via TypeDescriptor.GetProperties(typeof(IListItem)).

So now, you have three options:

  • write a TypeDescriptionProvider that returns the properties for IListItem - I'm not sure this is feasible since you can't possibly know what the concrete type is given just IListItem
  • use the correctly typed list (List<User> etc) - simply as a simple way of getting an IList with a non-object indexer
  • write an ITypedList wrapper (lots of work)
  • use something like ArrayList (i.e. no public non-object indexer) - very hacky!

My preference is for using the correct type of List<>... here's an AutoCast method that does this for you without having to know the types (with sample usage);

Note that this only works for homogeneous data (i.e. all the objects are the same), and it requires at least one object in the list to infer the type...

// infers the correct list type from the contents
static IList AutoCast(this IList list) {
    if (list == null) throw new ArgumentNullException("list");
    if (list.Count == 0) throw new InvalidOperationException(
          "Cannot AutoCast an empty list");
    Type type = list[0].GetType();
    IList result = (IList) Activator.CreateInstance(typeof(List<>)
          .MakeGenericType(type), list.Count);
    foreach (object obj in list) result.Add(obj);
    return result;
}
// usage
[STAThread]
static void Main() {
    Application.EnableVisualStyles();
    List<IListItem> data = new List<IListItem> {
        new User { Id = "1", Name = "abc", UserSpecificField = "def"},
        new User { Id = "2", Name = "ghi", UserSpecificField = "jkl"},
    };
    ShowData(data, "Before change - no UserSpecifiedField");
    ShowData(data.AutoCast(), "After change - has UserSpecifiedField");
}
static void ShowData(object dataSource, string caption) {
    Application.Run(new Form {
        Text = caption,
        Controls = {
            new DataGridView {
                Dock = DockStyle.Fill,
                DataSource = dataSource,
                AllowUserToAddRows = false,
                AllowUserToDeleteRows = false
            }
        }
    });
}