LINQifying - getting rid of CollectionBase?
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
A quick workaround for this is to use the LINQ .Cast
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
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
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
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
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
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
The Voices of Reason
# re: LINQifying - getting rid of CollectionBase?
# re: LINQifying - getting rid of CollectionBase?
http://blogs.msdn.com/kcwalina/archive/2005/09/26/474010.aspx
http://blogs.msdn.com/fxcop/archive/2006/04/27/585476.aspx
# re: LINQifying - getting rid of CollectionBase?
Yes, Collection<T> has protected virtual methods you can override to customize behavior (InsertItem(), ClearItems(), RemoveItem(), etc)
http://msdn2.microsoft.com/en-us/library/ms132397.aspx
# re: LINQifying - getting rid of CollectionBase?
http://www.aspnetresources.com/blog/collection_interfaces_smell.aspx
Hope you find it helpful.
# re: LINQifying - getting rid of CollectionBase?
public void Sort() { ((List<Photo>)this.Items).Sort(); }
Alternatively, it's quite simple to add the sort methods from List<T> to an intermediate base class:
public class SortableCollection<T> : Collection<T> { public SortableCollection(IList<T> list) : base(list) { } public SortableCollection() { } public void Sort(IComparer<T> comparer) { if (0 != this.Count) { List<T> list = this.Items As List<T>; if (null != list) { list.Sort(comparer); } else { list = new List<T>(this.Items); list.Sort(comparer); this.Items.Clear(); foreach (T item in list) { this.Items.Add(item); } } } } public void Sort(Comparison<T> comparison) { IComparer<T> comparer = null; if (null != comparison && 0 != this.Count) { comparer = new ComparisonComparer(comparer); } this.Sort(comparer); } public void Sort() { this.Sort((IComparer<T>)null); } private sealed class ComparisonComparer : IComparer<T> { private readonly Comparison<T> _comparison; public ComparisonComparer(Comparison<T> comparison) { _comparison = comparison; } public int Compare(T x, T y) { return _comparison(x, y); } } }
# re: LINQifying - getting rid of CollectionBase?
List<T> list = this.Items As List<T>;
should read:
List<T> list = this.Items as List<T>;
comparer = new ComparisonComparer(comparer);
should read:
comparer = new ComparisonComparer(comparison);
# Why not use KeyedCollection?
protected override string GetKeyFromItem(Photo item) { return photo.FileName.ToLower(); }
# re: LINQifying - getting rid of CollectionBase?
Dave
# re: LINQifying - getting rid of CollectionBase?
Maybe I should search your blog instead of MSDN!!
Thanking is the least I can do in respect to sharing these wonderful tips, tricks and ideas.
# re: LINQifying - getting rid of CollectionBase?
Looping over the entire collection and converting each filenames hash value to a string and comparing it to what has been passed is very inefficient.
Dictionary<TKey, TValue) is designed for exactly that purpose and defining Dictionary<string, Photo> would reduce the code and make lookups much faster.
[)amien