tags:

views:

197

answers:

2

Linq does a lot of clever things such as returning the result of the Count-property using the Count() method on a IList. Is there a good source that gives an overview of this optimizations?

It would be very interesting because as before I knew the above, I never used Count() and thus often returned a List<T> than only an IEnumerable<T> because i knew that the caller will need need often the instance-count of the list.

But having in mind that Count() does not really count the instances contained in the IEnumerable<T> but returns the result of the Count-property from the returned List and therefore not loosing performance occasioned me to change a lot of my returning types from a List to IEnumerable<T>.

+13  A: 

Try the .NET Reflector. It's a great tool for browsing class libraries, it has a powerful decompiler which let's you view the source code pretty much as it was written.

e.g. The Count() extension method is implemented like this

if (source == null)
{
    throw Error.ArgumentNull("source");
}
ICollection<TSource> is2 = source as ICollection<TSource>;
if (is2 != null)
{
    return is2.Count;
}
ICollection is3 = source as ICollection;
if (is3 != null)
{
    return is3.Count;
}
int num = 0;
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
    while (enumerator.MoveNext())
    {
        num++;
    }
}
return num;

At the off chance that the source does not implement the collection interface you'll have to count to get the actual acount. Browsing the code this way is a great way to learn.

John Leidegren
+5  A: 

Current optimisations that I'm aware of:

  • Count uses the Count property if the sequence implements ICollection<T> and a predicate isn't used. (In .NET 4 Count is also optimised for the non-generic ICollection.)

  • ElementAt/ElementAtOrDefault access by index if the sequence implements IList<T>.

  • Last/LastOrDefault access by index if the sequence implements IList<T> and a predicate isn't used.

  • ToArray/ToList use the Count property to allocate memory more efficiently if the sequence implements ICollection<T>. (But neither of them optimise for ICollection.)

Optimisations that could be there but aren't:

  • Last/LastOrDefault don't optimise in the case where a predicate is used. There's no reason why they couldn't optimise for IList<T>, iterating backwards through the list and accessing each element by index.

  • SequenceEqual could optimise for ICollection<T> and ICollection, using the Count property to determine if the lists are the same length and breaking out early if they're not.

  • Skip could optimise for IList<T>, accessing the elements by index and starting directly at index n rather than iterating and discarding the first n elements.

  • ToArray/ToList could also optimise for ICollection, using the Count property to allocate memory more efficiently.

  • ToDictionary could optimise for ICollection<T> and ICollection, using the Count property to allocate memory more efficently.

LukeH
I've researched this in depth using Reflector while developing the [Nito.Linq](http://nitolinq.codeplex.com/) library. Luke's answer is pretty complete. I would only add that `ToList` and `ToArray` do use `Count` to reduce memory reallocations. Also, `Empty` and `SequenceEqual` could be potentially optimized by using `Count`. Finally, there's also a potential for optimizing `Reverse`, but it's controversial because it changes the semantics [from buffering to streaming](http://msmvps.com/blogs/jon_skeet/archive/2010/03/25/just-how-lazy-are-you.aspx).
Stephen Cleary
@Stephen: I forgot all about the possible optimisation of `SequenceEqual`, even though I have an optimised version in my own LINQ helper library. I'll add it to the list. Could you elaborate on how `Empty` might be optimised? As far as I'm aware, it just returns a singleton empty sequence.
LukeH
@Luke: You're correct; I double-checked my notes and `Empty` was put in my comment by mistake. Sorry...
Stephen Cleary