views:

192

answers:

1

Important The question is not "What does Queryable.OfType do, it's "how does the code I see there accomplish that?"

Reflecting on Queryable.OfType, I see (after some cleanup):

    public static IQueryable<TResult> OfType<TResult>(this IQueryable source)
    {
        return (IQueryable<TResult>)source.Provider.CreateQuery(
            Expression.Call(
                null, 
                ((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(
                    new Type[] { typeof(TResult) }) ,
                    new Expression[] { source.Expression }));
    }

So let me see if I've got this straight:

  1. Use reflection to grab a reference to the current method (OfType).
  2. Make a new method, which is exactly the same, by using MakeGenericMethod to change the type parameter of the current method to, er, exactly the same thing.
  3. The argument to that new method will be not source but source.Expression. Which isn't an IQueryable, but we'll be passing the whole thing to Expression.Call, so that's OK.
  4. Call Expression.Call, passing null as method (weird?) instance and the cloned method as its arguments.
  5. Pass that result to CreateQuery and cast the result, which seems like the sanest part of the whole thing.

Now the effect of this method is to return an expression which tells the provider to omit returning any values where the type is not equal to TResult or one of its subtypes. But I can't see how the steps above actually accomplish this. It seems to be creating an expression representing a method which returns IQueryable<TResult>, and making the body of that method simply the entire source expression, without ever looking at the type. Is it simply expected that an IQueryable provider will just silently not return any records not of the selected type?

So are the steps above incorrect in some way, or am I just not seeing how they result in the behavior observed at runtime?

+3  A: 

It's not passing in null as the method - it's passing it in as the "target expression", i.e. what it's calling the method on. This is null because OfType is a static method, so it doesn't need a target.

The point of calling MakeGenericMethod is that GetCurrentMethod() returns the open version, i.e. OfType<> instead of OfType<YourType>.

Queryable.OfType itself isn't meant to contain any of the logic for omitting returning any values. That's up to the LINQ provider. The point of Queryable.OfType is to build up the expression tree to include the call to OfType, so that when the LINQ provider eventually has to convert it into its native format (e.g. SQL) it knows that OfType was called.

This is how Queryable works in general - basically it lets the provider see the whole query expression as an expression tree. That's all it's meant to do - when the provider is asked to translate this into real code, that's where the magic happens.

Queryable couldn't possibly do the work itself - it has no idea what sort of data store the provider represents. How could it come up with the semantics of OfType without knowing whether the data store was SQL, LDAP or something else? I agree it takes a while to get your head round though :)

Jon Skeet
OK, have I got this straight? 1. The call to MakeGenericMethod is needed because you can pass an open version of OfType, with an argument of YourType (separately), but you cannot pass OfType<YourType> as one argument. That part at least makes sense. 2. The point of all of this is to simply tell the provider "go implement OfType," rather than to produce an Expression of an implementation of OfType. Continued in next comment...
Craig Stuntz
However, it can't just return something along the lines of Expression.OfType (what I would expect, based on your explanation) because that doesn't actually exist. So instead it return something which amounts to the same thing, only is implemented differently for some reason?
Craig Stuntz
No, it returns something which represents a call to `Queryable.OfType`, just like `Select` returns an `IQueryable<T>` which represents the previous query with a chained call to `Queryable.Select`, etc. It's not an *expression* of `OfType`, it's an expression representing a *call* to `OfType`.
Jon Skeet
Got it. Thanks for the clarification!
Craig Stuntz