Reputation: 27001
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 the Northwind sample 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<LINQPad.User.Customers>' cannot be used as type parameter 'E' in the generic type or method 'Extensions.MySelect2<E, T, V>(E, Func<T, V>)'. There is no implicit reference conversion from 'System.Collections.Generic.IEnumerable<LINQPad.User.Customers>' to 'System.Linq.IQueryable<LINQPad.User.Customers>'.
Why? And how can it be fixed? Please help.
Upvotes: 2
Views: 1298
Reputation: 1500225
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.
Upvotes: 11