So I'm refactoring some code for an old side project, which is my old photoalbum app. I have built this thing back in the 1.x days and then updated it for 2.x and now it's going through another update for 3.5 just for fun. It's a nice side project that lets me play a bit with various AJAX features in jQuery.
[Updated: 03/27/2008 - updated thanks to suggestions from commenters]
Anyway I turned the project into a 3.5 project because it uses a number of collections that are manipulated in a variety of ways and LINQ could reduce and cut a bunch of code out of this app. Unfortunately I realized that I had originally implemented my photo collection as a CollectionBase which on its own is not LINQ compatible. LINQ expects types that implement IEnumerable and CollectionBase does not. All of the Generic Collections implement IEnumerable but the old collection types in System.Collections obviously don't since they predate Generics.
A quick workaround for this is to use the LINQ .Cast() method which can force a cast to IEnumerable. For example I can use the following on the CollectionBase derived type.
this.List.Cast<Photo>().Where(photo => photo.FileName.GetHashCode().ToString("x") == hashcode);
While this works, this is kind of ugly and adds clutter.
Another approach is to actually implement IEnumerable on the collection type. Implementing IEnumerable is pretty trivial when coupled with the yield return keyword in C#. If you have a CollectionBase type Implementing IEnumerable will do the trick:
public class PhotoCollection : CollectionBase, IEnumerable<Photo>
and then adding the implementation:
public new IEnumerator<Photo> GetEnumerator()
{
foreach (Photo photo in this.List)
{
yield return photo;
}
}
Now using the PhotoCollection works without having to cast:
this.List.Where(photo => photo.FileName.GetHashCode().ToString("x") == hashcode);
That does the trick in making the list easily LINQ accessible. Simple.
But if you were like me, implementing CollectionBase derived classes in .NET 1.1 merely to provide a basic strongly typed collections, and you didn't add explicit overrides of the collection base methods (like ensuring key field formatting for example) then an even better solution is to completely refactor out the Collectionbase implementation.
If that's the case you can simply derive from Collection and just create the collection like this:
public class PhotoCollection : Collection<Photo>
{
}
and that's it. You get basically a collection type that implements IEnumerable.
This may not always work though especially if you implemented a large number of CollectionBase methods. For example the Collection has no native Sort method to override and IList (which is what the Items collection uses) likewise has no native Sort method. In my case I also had a few additional methods that I had to carry over so my moved implementation of PhotoCollection looks like this:
public class PhotoCollection : Collection<Photo>
{
///
/// Hashcode based filename for ID embedding on the client
///
///
///
public Photo GetPhotoByHashCode(string Hashcode)
{
return this
.Where(photo => photo.FileName.GetHashCode().ToString("x") == Hashcode)
.FirstOrDefault();
//foreach( Photo Item in this.List)
//{
// if (Item.FileName.GetHashCode().ToString("x") == Hashcode)
// return Item;
//}
//return null;
}
///
/// Faked string key retrieval by filename
///
///
///
public Photo this[string Key]
{
get
{
Key = Key.ToLower();
return this
.Where(photo => photo.FileName == Key)
.FirstOrDefault();
//foreach (Photo Item in this)
//{
// if (Item.FileName == Key)
// return Item;
//}
//return null;
}
}
///
/// No internal .Sort method so implement our own
///
public void Sort()
{
//Array.Sort(this.Items,0,this.Items.Count,null);
List<Photo> photos = this.Items
.OrderByDescending(photo => photo.SortOrder)
.ThenBy(photo => photo.FileName).ToList();
this.Items.Clear();
foreach(Photo photo in photos)
{
this.Items.Add(photo);
}
}
}
This replaces the original class im my code in its entirety.
If you don't need to override any methods and you just need plain List behavior you can also derive directly from one of the generic collection types. So this:
public class PhotoCollection : List<Photo>
is also valid, but keep in mind that List has no overridable methods. You do get a more full featured interface with List (or other generic collection types) so for example, Sort() behavior is automatically supported. For the example above deriving from List is actually slightly easier to avoid the extra code for Sort().
There are two non collection based methods here that were in the original class and so those need to be carried forward for compatibility. As was pointed out in the comments it might have been cleaner to use a Dictionary or KeyedCollection which would be more efficient than the indexer I implemented above, but frankly in most cases plain lists are easier to work with for iteration. Here the list is small and the string indexer is actually rarely used - most iteration is sequential once the list has been sorted and stored (in XML here).
As always - .NET offers lots of choices for doing essentially the same thing - and I admit freely I often forget just what collection class is the right one to use especially when keyed collections are used. The nuances of each are subtle and guidelines are not well documented.
Nevertheless, it's easy to create LINQ compatible collections from non-generic collections whether it's with simple .Cast() calls or by re-factoring existing collections to their generic counter parts a bit late...
Other Posts you might also like