DataView for objects: Implementation Part I

Posted on October 10, 2006

In my last post, I described the .NET Framework support for views of collections of arbitrary objects.  In short, the required interfaces are provided, but no implementations.  In this post I’ll describe what we need to do to roll our own view.

In a nutshell, we need a view class that implements IBindingListView to wrap our collection of objects.  Our view will provide sorting and filtering, and neither of these operations will change the underlying collection.

Prior Art

To start, I’d like to acknowledge the work of others that was invaluable to my understanding (and will be to yours).

Michael Weinhardt wrote a series of articles for MSDN (November 2004 – April 2005) that walk through the basics of custom data binding:

Custom Data Binding, Part 1
Custom Data Binding, Part 2
Custom Data Binding, Part 3

These excellent articles show an in-depth implementation of IBindingListView in a custom collection deriving from BindingList<T>.  Bear in mind that the code in these articles is based on beta versions of .NET 2.0, so in some areas doesn’t correspond exactly to the classes in the released version of the .NET Framework.

Rockford Lhotka wrote an article for MSDN (January, 2004) that describes the implementation of a view implementing IBindingList:

Sorting the Unsortable Collection

Finally, Brian Noyes wrote a tremendous book on data binding for .NET 2.0, which includes an implementation of IBindingListView in a custom collection:

Data Binding with Windows Forms 2.0: Programming Smart Client Data Applications with .NET

Brian is a great resource for both data binding and also ClickOnce .NET deployment.  You should check out his blog.

Getting Started

Assumptions.  We know that our view class will essentially be a wrapper around an existing collection of objects.  Further, I’ll make the assumption that this collection is homogeneous – every item in the collection is of the same type.  I won’t assume that the collection is generic (e.g. List<T>), because I’d like to support simple lists such as ArrayList.  Lastly, I’ll assume that the collection is an IList, so that we know it supports index-based access to the items in the collection.  From this point on, I’ll refer to the underlying collection as the list, and the objects in the list as the items. We’ll talk about some additional important aspects of the list type and the item type later.

Approach.  Many of the methods of the view delegate to the methods of the list.  Since the view will be implementing IList, we’ll be providing a set of methods and properties in the view that duplicate those of our underlying list.  When we add items to the view, we’ll call list.Add(), and so on.  In fact, in the simple case of a view in an unsorted and unfiltered state, the IList methods could pass directly through to the list and still function correctly.  To work through the details of the implementation, I’m going to implement the various interfaces required for the view, from the simplest to the most complex.  We’ll start by implementing these methods in the view simply by calling the same method in the list, and make adjustments when the list fails to meet our needs.

ObjectListView

I’ll name our view class ObjectListView.  We’ll start with a very basic skeleton:

public ObjectListView(IList list)
{
  if (list == null)
    throw new ArgumentNullException("list");
  if (!this.IsListHomogeneous(list))
    throw new ArgumentException("The list contains multiple item types", "list");
  this.list = list;
}

For now, just assume that IsListHomogeneous() just checks that all of the items in the list are of the same type.

IEnumerable

Our first task is to implement this foundational behavior of any .NET collection.  IEnumerable contains one method, GetEnumerator().  The object returned by this method captures a position in the list, can return the item at this position, and can be advanced to the next item in the list.

We could start by implementing GetEnumerator() as list.GetEnumerator(), but we know that will be wrong if the view is sorted or filtered.  We want the enumerator to return the items in sorted order, ignoring any items that would be excluded by the filter.  For this task, we need a custom enumerator.  The enumerator will keep a reference to the view, and remember the version of the view that it was created with.  The version is just a counter that allows the enumerator to determine if the collection has changed since the enumerator was created.  If the version changes, the enumerator can no longer reliably continue iterating through the collection.  The rest of the enumeration implementation is straightforward.  Thinking ahead, we know that we will be implementing an indexer for our view, which will provide sequential access to the items in sorted, filtered order.  Thus, our enumerator can delegate it’s work back to the view:

private class ObjectListViewEnumerator : IEnumerator
{
  private int position = -1;
  private ObjectListView view;
  private int version;

  public ObjectListViewEnumerator(ObjectListView view)
  {
    if (view == null)
      throw new ArgumentNullException("view");
    this.view = view;
    this.version = view.version;
  }

  public object Current
  {
    get
    {
      if (this.version != this.view.version)
        throw new InvalidOperationException("The collection has been modified.");
      if (this.position > this.view.Count - 1)
        throw new InvalidOperationException("The enumerator is past the end of the collection.");
      if (this.position == -1)
        throw new InvalidOperationException("The enumerator is before the beginning of the collection.");
      return this.view[this.position];
    }
  }

  public bool MoveNext()
  {
    if (this.version != this.view.version)
      throw new InvalidOperationException("The collection has been modified.");
    if (this.position >= this.view.Count - 1)
    {
      return false;
    }
    else
    {
      this.position++;
      return true;
    }
  }

  public void Reset()
  {
    if (this.version != this.view.version)
      throw new InvalidOperationException("The collection has been modified.");
    this.position = -1;
  }
}

Note that ObjectListViewEnumerator is a nested class of ObjectListView, so it has access to the private members of ObjectListView.  The enumerator class is private since we only need to provide an IEnumerator to consumers.  The GetEnumerator() method of ObjectListView then becomes very simple:

public IEnumerator GetEnumerator()
{
  return new ObjectListViewEnumerator(this);
}

Where Are We?

We’ve implemented IEnumerable, the first of the five key interfaces in our view class.  In my next post, I’ll detail the implementation of the next interface, ICollection.


No Replies to "DataView for objects: Implementation Part I"


    Got something to say?

    Some html is OK