Implement Linq to Objects in C# 2.0
I’m still working mainly with Visual Studio 2005 at work, and I was really missing Linq to Objects features. And I’m sure I’m not the only one.
There are workarounds when compiling C#2.0 code using Visual Studio 2008 since it’s using the C#3.0 compiler internally, but it won’t work in VS2005.
How does linq to objects work ?
Linq to Object works by chaining operations on the IEnumerable<> interface.
When writing the following the following linq query statement
var paperBackTitles = from book in books
where book.PublicationYear == 2009
select book.Title;
The compiler translates it to :
IEnumerable<string> paperBackTitles = books
.Where(book => book.PublicationYear == 2009)
.Select(book => book.Title);
The lambdas are used as simple delegates in Linq to Object using the following definitions :
public delegate TResult Func<T,TResult>(T arg);
public delegate TResult Func<T1,T2,TResult>(T1 arg1, T2 arg2);
//...
public delegate bool Predicate<T>(T arg);
public delegate void Action<T>(T arg);
public delegate void Action<T1,T2>(T1 arg1, T2 arg2);
//...
So the preceding code is equivalent to :
IEnumerable<string> paperBackTitles = books
.Where(delegate(Book book){return book.PublicationYear == 2009;})
.Select(delegate(Book book){return book.Title;});
But the IEnumerable<> interface doesn’t provide those methods. These are actually extension methods defined in the Enumerable class.
public static class Enumerable
{
public static IEnumerable<T> Where<T>(
this IEnumerable<T> source,
Predicate<T> predicate);
public static IEnumerable<TResult> Select<T, TResult>(
this IEnumerable<T> source,
Func<T, TResult> projection);
//...
}
The translation is immediate :
IEnumerable<string> paperBackTitles =
Enumerable.Select(
Enumerable.Where(books,
delegate(Book book)
{ return book.PublicationYear == 2009; }),
delegate(Book book) { return book.Title; });
Once we’re here, there’s nothing that cannot be implemented in C#2.0.
What do we need ?
There is plenty of things in Linq to Object, and I prefer to say right now that we will not have the full integrated query syntax !
Implementing the static Enumerable class is not very difficult, let’s provide a implementation for Where and Select :
public static class Enumerable
{
public static IEnumerable<T> Where<T>(
IEnumerable<T> source,
Predicate<T> predicate)
{
if (source == null)
throw new ArgumentNullException("source");
if (predicate == null)
throw new ArgumentNullException("predicate");
return WhereIterator(source, predicate);
}
private static IEnumerable<T> WhereIterator<T>(
IEnumerable<T> source,
Predicate<T> predicate)
{
foreach (T item in source)
if (predicate(item))
yield return item;
}
public static IEnumerable<TResult> Select<T, TResult>(
IEnumerable<T> source,
Func<T, TResult> projection)
{
if (source == null)
throw new ArgumentNullException("source");
if (projection == null)
throw new ArgumentNullException("projection");
return SelectIterator(source, projection);
}
private static IEnumerable<TResult> SelectIterator<T, TResult>(
IEnumerable<T> source,
Func<T, TResult> projection)
{
foreach (T item in source)
yield return projection(item);
}
//...
}
You can notice that I’m splitting the methods in a part that does argument check and another one that makes the actual iteration process. This is because the iterator code will only get called when actually iterating, and it will be really hard to find out why the code throws an exception at that moment. By performing argument checking in a non-iterator method, the exception is thrown at actual method call.
Since C#2.0 doesn’t support extension methods we’ll have to find something so that the code doesn’t look ugly as in the final translation above.
Simulating extension methods in C#2.0
Extension methods are just syntactic sugar and are simply converted to a static method call by the compiler :
books.Where(predicate)
// is translated to
Enumerable.Where(books, predicate)
If we can wrap the books variable in a kind of C++ smart pointer providing the Where method, the trick is done.
To do this, we will use a small struct that encapsulate the IEnumerable<> interface :
public struct Enumerable<T> : IEnumerable<T>
{
private readonly IEnumerable<T> source;
public Enumerable(IEnumerable<T> source)
{
this.source = source;
}
public IEnumerator<T> GetEnumerator()
{
return source.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public Enumerable<T> Where(Predicate<T> predicate)
{
return new Enumerable<T>(
Enumerable.Where(source, predicate)
);
}
public Enumerable<TResult> Select<TResult>(
Func<T, TResult> projection)
{
return new Enumerable<TResult>(
Enumerable.Select(source, projection)
);
}
//...
}
The return type is Enumerable<> so that calls can be chained.
We can had a small helper to make the smart pointer creation shorter :
public static class Enumerable
{
public static Enumerable<T> From<T>(
IEnumerable<T> source)
{
return new Enumerable<T>(source);
}
//...
}
Now we can write :
IEnumerable<string> paperBackTitles =
Enumerable.From(books)
.Where(delegate(Book book){return book.PublicationYear == 2009;})
.Select<string>(delegate(Book book){return book.Title;});
We just have to extend the Enumerable class and Enumerable<> struct with more methods to get a full linq to object implementation in C# 2.0.