Why can't I replace IEnumerable<T> by a generic type variable in extension method?

I am trying to make an extension method more generic to avoid redundancy (Here is an example of some real code, the code below is just to demonstrate the issue - I had the idea to make the method available for IQueryable<T> as well).

The following works fine:

public static class Extensions
{
    public static IEnumerable<T> MySelect1<T, V>(this IEnumerable<T> query, Func<T, V> f)
    {
        // do something, then return IEnumerable<T>
        var result=query.AsEnumerable<T>();
        return result;
    }
    public static IQueryable<T> MySelect1<T, V>(this IQueryable<T> query, Func<T, V> f)
    {
        // do something, then return IQueryable<T>
        var result = query.AsQueryable<T>();
        return result;
    }
}

I can use it in LinqPad like (when connected with Northwind database):

var myQuery=(from x in Customers select x);
myQuery.AsEnumerable().MySelect1(d => d.CustomerID).Dump();
myQuery.AsQueryable().MySelect1(d => d.CustomerID).Dump();

Now I wanted to get rid of the duplicate implementation of MySelect1, so I refactored it as:

public static class Extensions
{
    public static E MySelect2<E, T, V>(this E query, Func<T, V> f)
    where E : System.Linq.IQueryable<T>, System.Collections.Generic.IEnumerable<T>
    {
        return (E)query.Select(f);
    }
}

This compiles too, but I cannot use MySelect2 the same way as I did above, consider the following:

// CS0411 The type arguments for method 'Extensions.MySelect2<E, T, V>(E, Func<T, V>)' 
// cannot be inferred from the usage. Try specifying the type arguments explicitly.
myQuery.AsEnumerable().MySelect2(d => d.CustomerID).Dump(); 
myQuery.AsQueryable().MySelect2(d => d.CustomerID).Dump();

Ok, doing what the error asks for works for this code line:

myQuery.AsQueryable()
       .MySelect2<IQueryable<Customers>, Customers, String>(d => d.CustomerID).Dump();

but not for that one:

myQuery.AsEnumerable<Customers>()
       .MySelect2<IEnumerable<Customers>, Customers, String>(d => d.CustomerID).Dump();

Here, I am getting

CS0311 The type 'System.Collections.Generic.IEnumerable' cannot be used as type parameter 'E' in the generic type or method 'Extensions.MySelect2(E, Func)'. There is no implicit reference conversion from 'System.Collections.Generic.IEnumerable' to 'System.Linq.IQueryable'.

Why? And how can it be fixed? Please help.

Jon Skeet
people
quotationmark

Why?

For exactly the reason stated in the error message: you're trying to use IEnumerable<Customers> as the type argument for E, but E has this constraint:

where E : System.Linq.IQueryable<T>

And how can it be fixed?

It can't, assuming I understand what you're trying to achieve.

There's a fundamental problem with the "simplification" you're trying to achieve: you don't actually have full duplication in your original MySelect1 methods. The first calls AsEnumerable() and the second calls AsQueryable(). You're trying to replace those with a cast, and that's just not going to work.

There's a further problem, even with your original methods: you're accepting Func<T, V> f as a parameter for your queryable-based method, which means any time you call Select or similar and passing in f, you'll be calling Enumerable.Select instead of Queryable.Select. To really use IQueryable<> properly, you should accept Expression<Func<T, V>> f instead. At that point, you won't need to call AsQueryable anyway.

Your two methods "should" take radically different paths based on whether you're using LINQ to Objects or a different LINQ provider (e.g. LINQ to SQL), and that can't be hidden as a pure implementation detail without significant changes that would probably make it less useful than you want anyway.

people

See more on this question at Stackoverflow