LINQ Entices Liskov Violations

Sunday, July 22, 2012 7:08 PM

In any relationship that lasts more than a few months, one begins to see character flaws in one’s beloved. One hopes that they are not fatal to the relationship, but they must be dealt with frankly and openly.

My readers know that I am deeply in love with LINQ. My only complaint until now has been that she has not made friends with my watchman but that’s more his fault than hers.

But today, I see a slight character flaw in her: she encourages violations of the Liskov Substitution Principle. To be sure, she only does this out of a desire to be helpful, but the clerk at the convenience store who sells alcohol to minors is only trying to be helpful, too.

Her flaw shows up in methods like the Count() and Last() extension methods on IEnumerable<T>.

An important species of IEnumerable<T> is the generator -- an IEnumerable<T> that yields a sequence such as the numbers in a mathematical progression. Such sequences can go on forever. What happens if the caller calls Count() or Last() on the returned IEnumerable<T>? Probably an infinite loop. The caller must know that he "should never do that" -- a sure sign of code smell.

(As an aside, one way around this problem is to cap the number of terms promised, as Chris Sells does with his Fibonacci sequence generator in Framework Design Guidelines, below. Count() and Last() then become meaningful, but they are still semantically "out of band" relative to the returned IEnumerable<long>, so there is still some code smell.)

```public static IEnumerable<long> GetSequence(int count)
{
long fib1 = 0;
long fib2 = 1;
yield return fib1;
yield return fib2; // assume they want at least 2, else what fun are they?
while(--count!= 1)
{
long fib3 = fib1 + fib2;
yield return fib3;
fib1 = fib2;
fib2 = fib3;
}
}```

The Liskov Substitution Principle implies that all implementations of an interface should work as expected. LINQ, by providing extension methods that work sometimes but not others, surely violates the principle.

In practice, this is rarely a problem because callers almost always do know that they "should never do that" and they behave themselves. So, LINQ and I have come to terms and we will happily continue our relationship.

I will feel even more at ease once I upgrade to .NET 4.5 and start to use IReadOnlyCollection<T> instead of IEnumerable<T>. That will be the subject of the next post.