Jesse Johnston
.NET Dev. Good times with .NET and coffee

A Few Things About Array Interfaces

Sunday, 21 January 2007 20:41 by jesse

One of the fun things about working with a big framework like .NET is that even after a few years of immersion in the technology, there are still fresh lessons to learn.  So today, in the "I did NOT know that" department, I present a few interesting things about the humble array.  The code snippets shown are part of a complete sample program that you can download here.

Arrays are enumerable

I did know this.  Array implements IEnumerable, so you can iterate over the items in the array.  There are two ways in C#.  The direct way is to obtain an IEnumerator via IEnumerable.GetEnumerator(), and walk through the array:

IEnumerable enumerable = new Customer[] { bobSmith, johnDoe, johnGalt };
IEnumerator enumerator = enumerable.GetEnumerator();
while (enumerator.MoveNext())
{
    Customer c = (Customer)enumerator.Current;
    Console.WriteLine(c.ContactName);
}

Of course, most of us would prefer the simpler foreach construct, which does the same thing with more syntactic finesse:

IEnumerable enumerable = new Customer[] { bobSmith, johnDoe, johnGalt };
foreach (Customer c in enumerable)
{
    Console.WriteLine(c.ContactName);
}

An Array is an ICollection

In fact, Array implements a number of collection interfaces.  Almost all are explicit interface implementations, which means that we have to cast to the collection type before we can use the methods and properties of the collection interface.

After IEnumerable, ICollection is the most primitive interface.  If we look at an array as an ICollection, we can find the number of array elements with the Count property (equivalent to Array.Length).  We can also see that an array is not synchronized (thread-safe), but it does offer a SyncRoot object for thread synchronization.  The SyncRoot object happens to be the array itself.

Customer[] customers = new Customer[] { bobSmith, johnDoe, johnGalt };
ICollection collection = customers;
Console.WriteLine("Count = " + collection.Count.ToString());
Console.WriteLine("IsSynchronized: " + collection.IsSynchronized.ToString());
Console.WriteLine("SyncRoot: " + collection.SyncRoot == null ? "(null)" : collection.SyncRoot.ToString());
Console.WriteLine("SyncRoot == customers? " + (object.ReferenceEquals(collection.SyncRoot, customers) ? "Yes" : "No"));

An Array is an IList

Now things get interesting.  The IsFixedSize property of IList returns true for an array, which makes sense.  You have to declare an array with a specific size.  However, Array does offers the Resize method, so is an array really fixed in size?  The answer is yes - the Resize method really creates a new array of the specified size and changes the reference from the original array to the new array (that's why the array parameter of Resize is a ref parameter).

The fixed size of Array has some other consequences.  As an IList implementation, Array must support Add(), Clear(), Insert(), Remove(), and RemoveAt().  Because the size of the array cannot be changed,  it doesn't really make sense to add or delete items.  All of these methods except Clear() throw NotSupportedException for this reason.  Clear() does work, although it does not affect the size of the array - it simply sets the value of each array item to null (or the zero-bit equivalent for a value type array item).

Generic Interfaces

With the advent of .NET 2.0, generics were added to the framework, including generic versions of the collection interfaces.  Thus we have IEnumerable<T>, ICollection<T>, and IList<T> alongside IEnumerable, ICollection and IList.  The generic interfaces allow us to refer to the array items in method parameters by their real types, instead of "object".  For example, instead of IList.Contains(object value), we can use IList<T>.Contains(T value), where T is the array element type.

This is perhaps intuitive, as an array instance is strongly typed when it is declared.  Array does in fact implement all of these generic interfaces.  An array of Customer objects is an IEnumerable<Customer>, ICollection<Customer>, and IList<Customer>.

As with the add/delete methods of IList, the corresponding methods of ICollection<T> and IList<T> throw NotSupportedException.  Interestingly, ICollection<T>.Clear() also throws NotSupportedException, which is inconsistent with the behavior of ICollection.Clear().

The support of generic interfaces in Array is very convenient.  it allows for easy initialization of generic collections with arrays:

Customer[] customers = new Customer[] { bobSmith, johnDoe, johnGalt };
 
// Constructors requiring IList<T> can use array of T.
Collection<Customer> collection = new Collection<Customer>(customers);
BindingList<Customer> bindingList = new BindingList<Customer>(customers);
 
// Constructor requiring IEnumerable<T> can use array of T.
List<Customer> list = new List<Customer>(customers);

And of course, methods that return an IList<T>, ICollection<T>, or IEnumerable<T> can simply return an array of T.

Who knew arrays were so cool?

kick it on DotNetKicks.com

Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList
Categories:   .NET
Actions:   E-mail | del.icio.us | Permalink | Comments (0) | Comment RSSRSS comment feed

Generics in ObjectListView

Tuesday, 2 January 2007 00:07 by jesse

Note: You can download the complete implementation here.

My original implementation of ObjectListView wraps a list of arbitrary objects.  It exposes these list items as type "object", which means that you have to cast the returned items to your list item type.  For example:

List<SimpleClass> list = new List<SimpleClass>();
ObjectListView view = new ObjectListView(list);
 
list.Add(new SimpleClass(1, "aaa", DateTime.Now));
list.Add(new SimpleClass(5, "bbb", DateTime.Now));
 
view.FilterPredicate = delegate(object listItem) { return ((SimpleClass)listItem).IntegerValue == 5; };
 
Assert.AreEqual("bbb", ((SimpleClass)view[0]).StringValue);

Here we have to cast the result of view[0] back to SimpleClass.

Wouldn't it be easier to use an ObjectListView that exposed the list items as their actual type?

ObjectListView<T>

You asked for it, and here it is.  In version 1.0.0.7, I've added a version of ObjectListView that fully supports generics.  The basic idea is that ObjectListView<T> takes an IList<T> as it's constructor argument.  The original ObjectListView takes a regular IList argument.  Thus, we can re-write the above example this way:

List<SimpleClass> list = new List<SimpleClass>();
ObjectListView<SimpleClass> view = new ObjectListView<SimpleClass>(list);
 
list.Add(new SimpleClass(1, "aaa", DateTime.Now));
list.Add(new SimpleClass(5, "bbb", DateTime.Now));
 
view.FilterPredicate = delegate(SimpleClass listItem) { return listItem.IntegerValue == 5; };
 
Assert.AreEqual("bbb", view[0].StringValue);

Because List<T> implements both IList and IList<T>, we can use the same underlying List<SimpleClass> for ObjectListView and ObjectListView<SimpleClass>.

Note that in addition to the indexer returning SimpleClass (e.g. view[0]), the FilterPredicate property is now a delegate type that takes an instance of the list item type (T) as a parameter, instead of type object.

Differences between ObjectListView and ObjectListView<T>

With ObjectListView<T>, the list item type is expressed right in the declaration of the view: ObjectListView<SimpleClass> is a view of SimpleClass list items.  This means that the ItemType property is not needed in ObjectListView<T>.  It also obviates the need for internal checks to see that the list item type is specified and that the list items are homogeneous.

Interestingly, IList<T> does not derive from IList.  This has important ramifications for ObjectListView.  It's primary role is to implement IBindingListView, which derives from IBindingList, IList, ICollection, and IEnumerable.  So we're not off the hook for supporting those interfaces.  Assuming that the consumer of ObjectListView<T> will be most interested in using the generic interfaces, I've chosen to make the weakly-typed interfaces explicit implementations.  This means that both IList and IList<T> are supported, but the methods of IList<T> are the public ones.  If you want to access an IList method, you just need to cast ObjectListView<T> to an IList first.

ICollection<T> (the base of IList<T>) doesn't offer the IsSynchronized or SyncRoot members of ICollection.  The consequence of this is that ObjectListView<T> need not worry about thread synchronization with the underlying list; there isn't anything to synchronize with.  Similarly, IList<T> does not provide the IsFixedSize property of IList, which eliminates more housekeeping code.

Both the event arguments of the AddingNew event and the ObjectView wrapper returned by AddNew() are strongly typed now.

As noted in the code example above, the ListItemFilter delegate type used by the FilterPredicate property is also strongly typed.

Updated Demo

I changed the Master/Details demo included in the download to use the generic version of ObjectListView.  I also added menu options to demonstrate filtering the list displayed in the grid.

Of course, the original "non-generic" version of ObjectListView is still included in the download, so if you prefer that, by all means use it!

Happy New Year!

kick it on DotNetKicks.com

Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList