Wednesday, June 30, 2010

A better, more type-safe, alternative to Cast<T>()

I ran into a limitation in the Cast extension method included with LINQ (Enumerable.Cast<TResult>(this IEnumerable source)), specifically that it does not allow for explicit/implicit conversion operators. The reason for this is that it is an extension method on IEnumerable, not IEnumerable<T>. Therefore the “Current” property (see previous post on IEnumerable<T>) is of type Object, and it can’t perform the cast from type A to Object to type B if there’s a conversion operator on either type.

I wrote a quick extension method to handle this:

public static IEnumerable<TDest> CastAll<TItem, TDest>(this IEnumerable<TItem> items)
{
var p = Expression.Parameter(typeof(TItem), "i");
var c = Expression.Convert(p, typeof(TDest));
var ex = Expression.Lambda<Func<TItem, TDest>>(c, p).Compile();

foreach (var item in items)
{
yield return ex(item);
}
}

Basically it builds an Expression that takes TItem, performs a type-safe Convert expression to TDest, and compiles that expression to a lambda of type Func<TItem, TDest> once per call (this could be cached outside of the method), and calls this new function on each item. Now it does a direct cast from TItem to TDest, instead of the type-unsafe A to Object to B casting that Cast<T> does.


Hope this helps!

3 comments:

ImaginaryDevelopment said...

So if this is a Convert instead of a cast, should it be called ConvertAll?

Charles Strahan said...

Why use the expressions? You're ultimately creating IL that corresponds to a cast like `(ThisType)thatObject`, so why not skip the overhead and just cast?

Paul said...

@charles - because that's what Cast's iterator block does, and it doesn't work with widening/narrowing operators. Here's Cast's magic:

this.5__b2 = this.<>7__wrapb3.Current;
this.<>2__current = (TResult) this.5__b2;
this.<>1__state = 2;
return true;

The generic cast doesn't check for widening/narrowing operators, it throws an error saying they're incompatible types.