Derived classes must be substitutable for their base classes
Simple and to the point, my favourites for definitions, which can be rephrased as “Subclasses must stick to their parent’s contracts”. Bad things can happen when descendants do not honour caller expectations.
Last week I found something as disturbing as this general rule violation and closely related to failure to meet expectations.
I was developing using cutting edge (not deprecated at all) technology: Linq2Sql </sarcasm>. I created a custom extension method to the IQueryable<T> type that received a simple enumeration that indicated the direction of ordering and will dispatch the call to OrderBy() or OrderByDescending(). Concretely I created a custom extension method that invoked OrderBy<TSource, TKey>(IQueryable<TSource>, Expression<Func<TSource, TKey>>, IComparer<TKey>) with a null comparer.
I unit tested my extension methods (using enumeration as my subject of choice) and everything looked correct.
But, and there always has to be a but for a blog post to happen… What would be my surprise to watch the extension method fail in the real, Linq2Sql-ruled world.
”Maybe null is not welcome”, I thought. I passed a Comparer<T>.Default instance just to see it fail again.
The message was not very descriptive and was related to the expression visitor. And then it struck me. Obvious when you think about it: the Linq2Sql provider cannot translate a .Net IComparer<T> into any SQL construct and therefore it explodes. As I see it the implementation team had two options:
- throw and tell what’s wrong
- ignore what they are not able to support
I like that decision they took of throwing (although the message could be a bit clearer) instead of silently not making use of a feature and potentially surprising me when the results do not meet my expectations.
I can also understand why this happens: queryable extensions are not “property” of Linq2Sql or Linq2Entities or any other provider for what matters. They are genetic extensions that depend on the underlying provider (of which Linq2Sql happens to be the first one to be released) to serve as a translator into something “useful”.
Of course there might be providers that are able to interpret this construction into something in their target domain of the problem, who knows.
But it surprises a lot the the first implementation of the technology fails to comply with its more basic contract: a method call. And it is not that is a very obscure expression construction, it is a straight extension method call.
What’s more, the more successful the platform becomes and the more providers are created, the more of these violations are going to proliferate.
Type-safe? Sure. Universally safe? Not so much. And it cannot be