Partial generic type inference possible in C#?

I wanted to create an extension method that could enumerate over a list of things, and return a list of those things that were of a certain type. It would look like this:

listOfFruits.ThatAre<Banana>().Where(banana => banana.Peel != Color.Black) ...

Sadly, this is not possible. The proposed signature for this extension method would have looked like:

public static IEnumerable<TResult> ThatAre<TSource, TResult>
    (this IEnumerable<TSource> source) where TResult : TSource

… and the call to ThatAre<> fails because both type arguments need to be specified, even though TSource may be inferred from the usage.

Following the advice in other answers, I created two functions: one which captures the source, and another which allows callers to express the result:

public static ThatAreWrapper<TSource> That<TSource>
    (this IEnumerable<TSource> source)
{
    return new ThatAreWrapper<TSource>(source);
}

public class ThatAreWrapper<TSource>
{
    private readonly IEnumerable<TSource> SourceCollection;
    public ThatAreWrapper(IEnumerable<TSource> source)
    {
        SourceCollection = source;
    }
    public IEnumerable<TResult> Are<TResult>() where TResult : TSource
    {
        foreach (var sourceItem in SourceCollection)
            if (sourceItem is TResult) yield return (TResult)sourceItem;
        }
    }
}

This results in the following calling code:

listOfFruits.That().Are<Banana>().Where(banana => banana.Peel != Color.Black) ...

… which isn’t bad.

Notice that because of the generic type constraints, the following code:

listOfFruits.That().Are<Truck>().Where(truck => truck.Horn.IsBroken) ...

will fail to compile at the Are() step, since Trucks are not Fruits. This beats the provided .OfType<> function:

listOfFruits.OfType<Truck>().Where(truck => truck.Horn.IsBroken) ...

This compiles, but always yields zero results and indeed doesn’t make any sense to try. It’s much nicer to let the compiler help you spot these things.

Leave a Comment