15

imagine a class, let's say for pagination which could be used with an IList<T> or an IQueryable<T>.

That class would have an int TotalItems property, which would (not that surprising) get / set the count of the queryable or enumerable parameter.

If I use IEnumerable<T> as parameter,

//simplified
public Pagination(IEnumerable<T> query)
    {
        TotalItems = query.Count();
    }

the Count() method will be (if I'm not wrong, that's the point) Enumerable.Count(). So even if query is an IQueryable<T> (which inherits from IEnumerable<T>), it will be enumerated (which is obviously not desired with a "db query").

So is there a way to use Queryable.Count() method when my IEnumerable<T> is in fact an IQueryable<T>, or do I have to change my design, having, for example in this case, 2 ctor

//simplified
public Pagination(IEnumerable<T> query)
    {
         TotalItems = query.Count();
    }
public Pagination(IQueryable<T> query)
    {
         TotalItems = query.Count();
    }

EDIT I do understand that IQueryable<T> inheriting from IEnumerable<T> has nothing to do with the fact that IEnumerable<T> and IQueryable<T> have extension methods with same name, and that it's nice to have same names for extension method, which "look like they do the same", but I think it still sometimes confusing...

Generic question for curiosity

Are they other examples, in framework, with the same "architecture" : inheritance + common names for extension methods ?

Raphaël Althaus
  • 59,727
  • 6
  • 96
  • 122
  • why not just accept an `int` and let them count a sequence/query if that's where the count comes from? – Servy Mar 08 '13 at 15:34
  • @AakashM well, this question comes after a real try, and a look at MiniProfiler results : I may have done a bad interpretation, in that case, I'll smash my head on a wall ;) – Raphaël Althaus Mar 08 '13 at 15:35
  • @Servy as pointed, this is a simplified case, I might want to use the `query` parameter for other purpose in my class, and call other "shared" queryable or Enumerable extension methods, which would have the same problem (if the problem exists). – Raphaël Althaus Mar 08 '13 at 15:37
  • Hi, if i am not wrong...if IEnumerable is the parameter and you pass it IQueryable, then query.Count() will call the method Count defined with IQueryable ( default polymorphism behavior )?? – Dinesh Mar 08 '13 at 16:12
  • 2
    @DnshPly9 that's where the thing is "confusing". Count() is not a method of `IQueryable` or `IEnumerable`, it's, or to be correct "they are" extension methods... So the extension methods have just the same name, but not related at all with polymorphism mechanism. – Raphaël Althaus Mar 08 '13 at 16:15

2 Answers2

8

You should have two constructor overloads, as you showed in the question. This makes it up to the caller whether they want the IQueryable methods to be used, or the IEnumerable methods to be used.

If someone were to do:

Pagination pager = new Pagination(query.AsEnumerable());

Then they clearly want the object to be processed as an IEnumearble, not an IQueryable. Perhaps they know that Skip and Take aren't implemented by their query provider and so the pagination will fail, and it needs to be evaluated as Linq-to-objects.

By having the two overloads you make it an informed decision by the user of your class whether they're dealing with an in memory sequence or a query, rather than trying to figure it out on your own.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • +1, good example with Skip/Take that might be not supported by the query provider. – ken2k Mar 08 '13 at 16:41
  • +1 also, good example of not implemented extension methods on different providers. Soon accepted, just a question : don't you think that I could then have the ctor with IEnumerable parameter replaced by an IList parameter : knowing that Count() will enumerate anyway, this would make things clearer, no ? – Raphaël Althaus Mar 10 '13 at 12:12
1

Well, you could do:

TotalItems = enumerable.AsQueryable().Count();

This would directly use the query-provider's Count implementation for a native queryable, or fallback to LINQ to Objects (with some overhead for using an EnumerableQuery) otherwise.

Another solution (potentially more efficient):

var queryable = enumerable as IQueryable<T>;
TotalItems = queryable != null ? queryable.Count() : enumerable.Count();

However, note that if your queryable implements ICollection or ICollection<T> efficiently (as is the case with some query providers), even your existing solution would potentially work well because of optimizations within LINQ (it uses the Count property from those interfaces where possible). For your IList<T>, it will always use this optimization since it implements ICollection<T>.

Ani
  • 111,048
  • 26
  • 262
  • 307
  • 3
    This could cause problems though. If someone has an `IQueryable` and they call `AsEnumerable` on it it means that they *want* it to be resolved as LINQ-to-objects, and not perform subsequent queries against the query provider. – Servy Mar 08 '13 at 15:43
  • @Servy well, would it really be performed (again) against the query provider, as it will have been enumerated with `AsEnumerable()` ? Must admit I'm not that clear on what's happen with an `IQueryable().AsEnumerable().AsQueryable()`... – Raphaël Althaus Mar 08 '13 at 16:03
  • 1
    @RaphaëlAlthaus No, `AsEnumerable` doesn't enumerate anything, it effectively doesn't do anything more than cast the object to `IEnumerable`. The full definition is: `public static IEnumerable AsEnumerable(this IEnumerable source) {return source;}` – Servy Mar 08 '13 at 16:06
  • @Servy: I don't understand your point. The OP *specifically* wants the query-provider's Count() implementation for native queryables. – Ani Mar 08 '13 at 16:26
  • @Ani But at the end of the day it should be up to the caller which is used, he just wants it to use the `Queryable` method *if they knowingly passed in an `IQueryable`, but not if they didn't. – Servy Mar 08 '13 at 16:27