Inclusively Take Elements using LINQ and Custom Extensions

I ran into an interesting LINQ problem recently, one which required me to extend LINQ in my own fashion. The problem was fairly simple. I’m trying to build a list of every day a course meets over the course of a term. I start by querying our Academic Calendar for every event in the calendar, then I skip all the events prior to the first day of class, taking all the events until the end of classes for the term. I couldn’t just take those two events, because different events in the middle of the term affect if courses are offered. My first draft at this problem was simple:

IEnumerable<AcademicEvent> _events = GetAcademicCalendarEvents
                            (data.Year, data.Term, data.Campus)
                .SkipWhile(c => c.EventType != "TermBegins")
                .TakeWhile(c => c.EventKey != "TermEnds");

This code is broken. But the error is subtle. The TakeWhile method is not inclusive. The resulting IEnumerable will not include the event with the Key “TermEnds”, resulting in my later code inadvertently not showing some days at the end of the term. Given that the event that follows the TermEnds event may not always be the same, I didn’t really want to try to key off of the next possible event. What I really wanted to do was take items on the list until I reached the TermEnds event. Sounds like an easy enough extension method!

public static IEnumerable<T> TakeUntil<T>
    (this IEnumerable<T> data, Func<T, bool> predicate)
{
    return data.TakeWhile(c => !predicate(c));
}

And since the messing around with the IEnumerable has already been done for me, I might as well take advantage of that work, and just reverse the predicate as I send it into the existing TakeWhile. Of course, this doesn’t work either. Because TakeWhile (and by extension, TakeUntil) will not return the item which triggered the predicate, which I want. So, another extension method is required, TakeUntilInclusive.

public static IEnumerable<T> TakeUntilInclusive
        (this IEnumerable<T> data, Func<T, bool> predicate)
{
    int index = 0;
    var enumerator = data.GetEnumerator();
    while (enumerator.MoveNext())
    {
        index++;
        if (predicate(enumerator.Current)) { break; }
    }
    enumerator.Dispose();
    return data.Take(index);
}

And while I’m at it…

public static IEnumerable<T> TakeWhileInclusive<T>
    (this IEnumerable<T> data, Func<T, bool> predicate)
{
    return data.TakeUntilInclusive(p => !predicate(p));
}

I’ve mentioned extension methods before, but I can honestly say that they are the single best reason for using C# 3, and .NET 3/3.5. They’re that good. Essentially, extension methods bring limited monkey-patching to the static compilation world, but do so in a really selective way. All the code above would be wrapped in a static class in a Namespace that would allow me to select when those methods were available or not. The only weakness to extension methods is that they do not allow you to override methods that already exist with new behavior. Really, it’s just a compiler hack, which is why it’s a language feature and not a runtime feature, but it’s a really good compiler hack.

Breaking down the syntax is easy. Any static method, located in a static class, with a first argument prefixed with the ‘this’ keyword, will be interpreted by the compiler as an extension method, and the compiler will allow the method to be called as object.Method(), instead of StaticClass.Method(object). Essentially, those two calls are functionally identical, but the extension method provides code that is cleaner and easier to read. However, it’s real power is when paired with interfaces and generics, as with LINQ, which my code above just adds on to.

The final version of the query looks like this:

IEnumerable<AcademicEvent> _events = GetAcademicCalendarEvents
                        (data.Year, data.Term, data.Campus)
                .SkipWhile(c => c.EventType != "TermBegins")
                .TakeUntilInclusive(c => c.EventKey == "TermEnds");

For clarity, I could create a SkipUntil wrapper around SkipWhile, but given that SkipWhile is doing what I want anyway, I didn’t feel it was necessary. Now, this code works against an IEnumerable, not an IQueryable, so it can really only be used on data sets small enough to be handled in memory. I’m not entirely sure how I’d implement this in an IQueryable, as that object expects to be able to translate to SQL (or similar) at some point, and this isn’t the sort of thing that I can think of how to do in a single query.

Writing Extension methods is really easy, and LINQ is the best example of their power so far. The fact that you can so easily extend upon this existing work is fantastic, and I can think of a few other problems I’m liable to have to solve in the coming months where I’ll be using these extension methods, or some new ones.