DataView for objects: Implementation Part II
Posted on October 12, 2006
Note: You can download the complete implementation here.
In my last post I described the implementation of the IEnumerable interface in ObjectListView, my implementation of a view for collections of arbitrary objects. The goal is to provide the same view capabilities for collections that DataView supports for DataTables.
In this post, we’ll move on to the next interface required for data binding, ICollection.
ICollection
This interface adds seemingly very little behavior to our view. There is the Count property, which returns the number of items in the collection, and the CopyTo method, which copies items from the collection to an array. We could delegate both of these to the corresponding implementations in the underlying list, but we want to reflect only the items in the list that meet the filter criteria (if any). Note that the filter is implemented in IBindingListView, so I’m thinking ahead here. Count will return only the number of items in the list that match the filter. The same applies to CopyTo; only the matching items will be copied. A further constraint on CopyTo is that we want it to copy items in their sorted order, if a sort has been applied to the view. Sorting is also introduced later, in the implementation of IBindingList. Let’s look at the implementation:
public int Count { get { Lock(); try { return this.GetSortIndexes().Count; } finally { Unlock(); } } } public void CopyTo(Array array, int index) { Lock(); try { for (int i = 0; i < this.Count; i++) array.SetValue(this[i], i + index); } finally { Unlock(); } }
First, looking at Count, we see that we’re returning the count of our sorted indexes. I’ll cover this method in my next post, when we get into the implementation of IList. For now, assume that we’re keeping a separate list of only the filtered items, in sorted order. Thus, the count we’re returning is the count of the items visible through the view. CopyTo() uses the indexer (this[i]) to retrieve the list items in sorted order; we’ll also cover the indexer in the discussion of IList.
What about Lock() and Unlock – why are these needed?
So far, we’ve made no claim to thread safety. However, ICollection adds the IsSynchronized and SyncRoot properties, which allow a collection to support thread-safe access. If the IsSynchronized property of our list is true, the collection is stating that it is thread-safe, and that it’s methods will lock on the object returned by SyncRoot when modifying the collection. In our implementation of these two properties, we can delegate directly to the list properties. If the list is not synchronized, the view cannot be either. The question is, if the list is synchronized, does the view need to be as well? I think the answer is yes. The view represents a potentially reordered subset of the list, but all other characteristics of the list should be faithfully reflected in the view. Thus, if the list is thread-safe, we’ll extend that behavior to the view.
The Lock() and Unlock() methods provide the locking coordination with the underlying list, if needed. Here is the locking code:
private void Lock() { if (this.synced) Monitor.Enter(this.SyncRoot); } private void Unlock() { if (this.synced) Monitor.Exit(this.SyncRoot); }
The value of the synced field is set in our constructor as follows:
this.synced = list.IsSynchronized && list.SyncRoot != null;
We’re making the assumption that the thread-safety behavior of the underlying list is invariant over it’s lifetime.
One final point to make about the locking behavior is the use of the try / finally blocks in CopyTo() and Count. If an exception is thrown while the ObjectListView is in a locked state, we want to guarantee that we unlock before propagating the exception. Because we’re locking on the SyncRoot provided by the list, remaining locked after throwing an exception could leave other consumers of the list deadlocked. We’ll see other interesting consequences of locking later in our implementation.
Where are we?
We’ve implemented the IEnumerable and ICollection interfaces, which expose relatively simple features. However, we’ve already identified the need for maintaining a separate list of sorted and filtered items, as well as respecting the thread-safety guarantees of the underlying list.
In my next post, I’ll move into the IList interface, where we finally expose list manipulation behavior such as adding and removing items.
Got something to say?