Our last operator is the simplest of all. Really, really simple.
What is it?
AsEnumerable has a single signature:
I can describe its behaviour pretty easily: it returns source.
That’s all it does. There’s no argument validation, it doesn’t create another iterator. It just returns source.
You may well be wondering what the point is… and it’s all about changing the compile-time type of the expression. I’m going to take about IQueryable<T> in another post (although probably not implement anything related to it) but hopefully you’re aware that it’s usually used for "out of process" queries – most commonly in databases.
Now it’s not entirely uncommon to want to perform some aspects of the query in the database, and then a bit more manipulation in .NET – particularly if there are aspects you basically can’t implement in LINQ to SQL (or whatever provider you’re using). For example, you may want to build a particular in-memory representation which isn’t really amenable to the provider’s model.
In that case, a query can look something like this:
.Customers
.Where(c => some filter for SQL)
.OrderBy(c => some ordering for SQL)
.Select(c => some projection for SQL)
.AsEnumerable() // Switch to "in-process" for rest of query
.Where(c => some extra LINQ to Objects filtering)
.Select(c => some extra LINQ to Objects projection);
All we’re doing is changing the compile-time type of the sequence which is propagating through our query from IQueryable<T> to IEnumerable<T> – but that means that the compiler will use the methods in Enumerable (taking delegates, and executing in LINQ to Objects) instead of the ones in Queryable (taking expression trees, and usually executing out-of-process).
Sometimes we could do this with a simple cast or variable declaration. However, for one thing that’s ugly, whereas the above query is fluent and quite readable, so long as you appreciate the importance of AsEnumerable. The more important point is that it’s not always possible, because we may very well be dealing with a sequence of an anonymous type. An extension method lets the compiler use type inference to work out what the T should be for IEnumerable<T>, but you can’t actually express that in your code.
In short – it’s not nearly as useless an operator as it seems at first sight. That doesn’t make it any more complicated to test or implement though…
What are we going to test?
In the spirit of exhaustive testing, I have actually tested:
- A normal sequence
- A null reference
- A sequence which would throw an exception if you actually tried to use it
The tests just assert that the result is the same reference as we’ve passed in.
I have one additional test which comes as close as I can to demonstrating the point of AsEnumerable without using Queryable:
public void AnonymousType()
{
var list = new[] {
new { FirstName = "Jon", Surname = "Skeet" },
new { FirstName = "Holly", Surname = "Skeet" }
}.ToList();
// We can’t cast to IEnumerable<T> as we can’t express T.
var sequence = list.AsEnumerable();
// This will now use Enumerable.Contains instead of List.Contains
Assert.IsFalse(sequence.Contains(new { FirstName = "Tom", Surname = "Skeet" }));
}
And finally…
Let’s implement it!
There’s not much scope for an interesting implementation here I’m afraid. Here it is, in its totality:
{
return source;
}
It feels like a fittingly simple end to the Edulinq implementation.
Conclusion
I think that’s all I’m going to actually implement from LINQ to Objects. Unless I’ve missed something, that covers all the methods of Enumerable from .NET 4.
That’s not the end of this series though. I’m going to take a few days to write up some thoughts about design choices, optimizations, other operators which might have been worth including, and a little bit about how IQueryable<T> works.
Don’t forget that the source code is freely available on Google Code. I’ll be happy to patch any embarrassing bugs :)
You can’t cast string[].OfType back to array of strings.
It would be consistent of AsEmunerable to have the same property: hide the identity of the source.
LikeLike
@Mihailik: Not OfType – but you can with Cast. Calling Cast on an array (with the element type as the type argument) will always return the original reference. See my post on Cast/OfType for details.
Note that OfType *couldn’t* make this optimization with an array of strings, as the array could contain null elements which shouldn’t be returned from OfType.
LikeLike