1

For performance reasons many of my services are returning IQueryables right now, so the caller can decide what elements to filter before they get materialized. For design reasons (interfaces etc.) I'd rather like to return IEnumerables but that sure removes the advantages of IQueryables: materialization of the complete result set plus no -Async methods.

I've started writing some extension methods that prefer IQueryable over IEnumerable, e.g.:

public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Expression<Func<TSource, bool>> predicate)
    => source is IQueryable<TSource> ? Queryable.Where(source as IQueryable<TSource>, predicate) : Enumerable.Where(source, predicate.Compile());

public static Task<TSource[]> ToArrayAsync<TSource>(this IEnumerable<TSource> source)
    => (source as IQueryable<TSource>)?.ToArrayAsync() ?? Task.FromResult(source.ToArray());

I can't be the only one thinking about this. Any improvements for these methods? Is there already a good collection / solution out there? Or is this a bad design? So please tell me reasons.

Thank you!

1
  • If you're going to use it as an IQueryable, why would you want to return an IEnumerable? It will only make less obvious what's going on. Commented Apr 10, 2021 at 9:19

1 Answer 1

4

Personally it would make me cautious; sequences and queries are similar but different enough that I very much want to be sure how it is being handled. There is also some non-trivial overhead involved in generating an expression tree and then compiling it, vs just using a direct compiler-generated method on the capture-context.the overhead of the expression tree is justifiable if it is going to be used as part of query composition and translation, but isn't needed in the sequence case.

Perhaps a more obvious approach would be to use AsQueryable() on a sequence to simulate a true query.

But: what you have should work, I guess. As a minor syntactic note, in recent C# versions you can capture the typed value as part of is to avoid a double cast:

public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Expression<Func<TSource, bool>> predicate)
    => source is IQueryable<TSource> q ? Queryable.Where(q, predicate) : Enumerable.Where(source, predicate.Compile());

It may also be interesting to experiment with the boolean flag on Compile() - using interpreted mode gives slower execution but avoids IL generation. It can be a positive effect for small data sets, and negative for large.

As a side note: there are multiple async implementations for queryable sources; EF Core has one, and IX (part of Reactive) has another. They are incompatible with each-other and use different approaches, but both can work depending on your scenario.

3
  • Thank you for your very detailed answer! And that extra code sugar! Didn't know it - i like! In my "framework" a have several services that need to return an IEnumerable by interface but can just return an IQueryable. If the do return an IQueryable I do not want all callers to check the actual return type but rather take the most performant one. So for my internal use case you'd say my approach is ok? Except that extra code sugar :)
    – tris
    Commented Apr 10, 2021 at 10:18
  • @tris it'll work; everything else is entirely subjective; there may be some overhead, but whether that matters is hugely contextual - so: we can't really comment - but: to go back to my AsQueryable comment: the code there already checks for "is IQueryable-T", so honestly: I'd just use AsQueryable(); citation: github.com/dotnet/runtime/blob/main/src/libraries/… (and also a few lines above for the generic version) Commented Apr 10, 2021 at 12:30
  • Yes, thank you - probably the best solution, actually just what I was looking for: weblogs.asp.net/zeeshanhirani/…
    – tris
    Commented Apr 10, 2021 at 22:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.