Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
Markdown Monster - The Markdown Editor for Windows

LINQifying - getting rid of CollectionBase?


:P
On this page:

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 : CollectionBaseIEnumerable<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...

Posted in .NET  LINQ  

The Voices of Reason


 

Damien Guard
March 26, 2008

# re: LINQifying - getting rid of CollectionBase?

Why are you using a List<T> to store something you want to be able to reference by a key?

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

Rick Strahl
March 26, 2008

# re: LINQifying - getting rid of CollectionBase?

Yeah a dictionary would be the right choice if I were starting from scratch. But in this case it'd change the semantics of the existing code. Can't replace this collection with a dictionary.

Jason
March 26, 2008

# re: LINQifying - getting rid of CollectionBase?

In regards to the last paragraph of the article, you can inherit from System.Collections.ObjectModel.Collection<T>. This is actually preferred over List<T> according to Krzysztof Cwalina and FxCop. It is intended for public APIs because it has virtual methods.

http://blogs.msdn.com/kcwalina/archive/2005/09/26/474010.aspx
http://blogs.msdn.com/fxcop/archive/2006/04/27/585476.aspx

Lucas
March 26, 2008

# re: LINQifying - getting rid of CollectionBase?

@Jason: I was just writing that!

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

Milan Negovan
March 27, 2008

# re: LINQifying - getting rid of CollectionBase?

Rick, I've struggled with this too and came up with a suitable enough solution (for me, at least):
http://www.aspnetresources.com/blog/collection_interfaces_smell.aspx

Hope you find it helpful.

Richard Deeming
March 27, 2008

# re: LINQifying - getting rid of CollectionBase?

Since you're only using the default constructor of the Collection<T> class, you could take advantage of the fact that the Items property will always be a List<T> instance:

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);
        }
    }
}

Richard Deeming
March 27, 2008

# re: LINQifying - getting rid of CollectionBase?

Whoops! A couple of typos in my previous post:


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);

David Keaveny
July 30, 2008

# Why not use KeyedCollection?

Why create the this[string Key] indexer? Just implement KeyedCollection<T>, and override the GetKeyFromItem method:

protected override string GetKeyFromItem(Photo item)
{
     return photo.FileName.ToLower();
}

David Sutherland
September 01, 2009

# re: LINQifying - getting rid of CollectionBase?

Great article. This really helped me with updating some old code. Thanks;

Dave

Achilles
August 08, 2010

# re: LINQifying - getting rid of CollectionBase?

You're my hero!
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.

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024