Implement Linq to Objects in C# 2.0
By Jérémie Chassaing on Tuesday, February 17, 2009, 15:55 - Linq - Permalink
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.
Comments
That's awesome, this was a brilliant idea!
Nice Post
Have you looked into http://code.google.com/p/linqbridge... (only works on VS 2008)
@leyu > I did not know LinqBridge but I knew the technic. But I still have to work with VS2005 on lots of projects and this is a way leverage part of the functional writting.
I really think that the biggest features in linq are lambdas and extension methods.
Lambdas because they're short, extension methods because you can make interfaces feature complete without bloating them with things that can be done from the outside, it enables method chaining.
i am newbie.... Can anybody provide me with an example of this for a clear understanding ... :)
Thanks
jan
@jan, I'm very busy this week end (you'll see in the next post :-P) but I'll provide more info soon.
Amazing article... tried implementing it but facing 1 last obstacle.. "Func" is not getting identified while compiling the code.. do we need to import any namespace to resolve this error.
@subby> true, Func doesn't exist in .net 2
You can simply declare it this way :
delegate TReturn Func<TReturn>();delegate TReturn Func<T1,TReturn>(T1 arg1);...it works..thnx a ton..