Simon Parker
Simon Parker

Reputation: 1834

A C# generic function for any container

I have a function that looks like this:

static public IQueryable<TSource> OrderData<TSource, TKey>(this IQueryable<TSource> source,
    System.Linq.Expressions.Expression<Func<TSource, TKey>> keySelector,
    Sort.SortDirection sortDirection)
{
    if (sortDirection == Sort.SortDirection.Ascending)
    {
        return source.OrderBy<TSource, TKey>(keySelector);
    }
    else
    {
        return source.OrderByDescending<TSource, TKey>(keySelector);
    }
}

Now this was great until I needed to do the same thing for an IEnumerable container. I could call this, casting the container on the way in and out, but I was wondering if there was a way of making the container itself a generic parameter, and still have it work.

I wanted something like:

static public C<TSource> OrderData<C, TSource, TKey>(this C<TSource> source,
    System.Linq.Expressions.Expression<Func<TSource, TKey>> keySelector,
    Sort.SortDirection sortDirection) where C : IEnumerable<TSource>

This doesn't compile, giving fundamental errors like "',' expectd". Any ideas?

Upvotes: 1

Views: 201

Answers (2)

vgru
vgru

Reputation: 51224

Tl;dr: you can always create a cheap IQueryable<T> wrapper over IEnumerable<T>, which is perfectly fine and would probably solve your problem:

IEnumerable<int> nums_enumerable = new[] { 1, 2, 3 }.AsEnumerable();
IQueryable<int> nums_queryable = nums_enumerable.AsQueryable();

The long version is that your problem shows why interfaces exist in the first place: to tell the compiler that, if two objects implement IEnumerable, then this is a contract by which they must implement all the methods in that interface and the call to these methods won't fail at runtime.

What you are asking is to ignore the interface completely, which is called duck typing and is actually even achievable in C# using reflection or the DLR (dynamic keyword). However, it really smells of bad design when you need to use reflection and you suddenly lose all the compile-time checks and guarantees: your code will maybe work at runtime, maybe not. In this case, it would be an utterly bad idea.

Examples for duck typing usually present simple use-cases, but the point is that the method signature still must match. In this particular case, it's even worse: various methods in this interface have the same name and seem to work the same way, they are completely different.

Compare these two cases, for example:

  1. In the IEnumerable<T> case, we are calling public static double Average<TSource>(this IEnumerable<TSource> source, Func<TSource, int> selector). The parameter to this method is an anonymous method, i.e. a method in a compile-time generated class which takes a parameter and returns the value multiplied by 2. The IEnumerable.Average method actually invokes this method for each item in the list:

    double GetAverage(IEnumerable<int> items)
    {
        return items.Average(i => i * 2);
    }
    
  2. In the IQueryable<T> case, we are calling public static double Average<TSource>(this IQueryable<TSource> source, Expression<Func<TSource,int>> selector). The parameter to this method is an expression tree, i.e. an object containing information that we want to multiply a parameter with 2. It literally contains a binary expression of type Multiply which references two other expressions (a ParameterExpression and a ConstantExpression). This object doesn't perform any calculations itself when passed to IQueryable.Average:

    double GetAverage(IQueryable<int> items)
    {
        return items.Average(i => i * 2);
    }
    

Upvotes: 0

Jon Skeet
Jon Skeet

Reputation: 1500785

No, there's no way in C# of expressing a type parameter which itself is some arbitrary type which must be generic with a specific arity. (That's what it looks like you're trying to do with C.)

It's also worth noting that you probably don't want to have the same signature for IEnumerable<T> anyway, as you'd normally work with delegates rather than expression trees for in-process querying. So you'd have

public static IEnumerable<TSource> OrderData<TSource, TKey>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector,
    Sort.SortDirection sortDirection)
{
    return sortDirection == Sort.SortDirection.Ascending
        ? source.OrderBy(keySelector)
        : source.OrderByDescending(keySelector);
}

Upvotes: 3

Related Questions