Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Poor Man's LINQ in Visual Studio 2005

4.53/5 (18 votes)
29 Oct 2010MIT3 min read 2   620  
A way to use LINQ to Objects in C# 2.0 with .NET Framework 2.0

Introduction

Language INtegrated Query in C# 3.0 is pure joy to use. Once you try it, you don't want to stop.

But a few of us, for whatever reason, are still using Visual Studio 2005. In my case, I didn't want to pay $550 for the upgrade to VS2008 Pro, but I didn't want to lose features like Macros and Code Diagrams by switching to Visual C# Express Edition or SharpDevelop. So, I came up with a way I could use LINQ-to-Objects in C# 2.0.

Note: If you are stuck with .NET Framework 2.0, but you are using C# 3.0 or Visual Studio 2008, you don't need the code in this article. Just use LinqBridge.

Using the Code

Here is some simple C# 3.0 LINQ code. This code contains a query and several calls to extension methods in the System.Linq.Enumerable class. Of course, since they are extension methods, the code doesn't actually mention Enumerable.

C#
using System;
using System.Linq;

class Program
{
    static void Main(string[] args)
    {
        string[] words = new string[] { 
            "Pies", "Are", "Good", 
            "In", "Lovely", "Apples" };
        // Pies Are Good
        Console.WriteLine(string.Join(" ", words.Take(3).ToArray()));
        // Apples Are Good In Lovely Pies
        Console.WriteLine(string.Join(" ", words.OrderBy(x => x).ToArray()));

        int[] numbers = new int[] { 4, 95, 309, 357, 233, 2 };
        // 1000
        Console.WriteLine(numbers.Sum());
        // 666
        Console.WriteLine((from x in numbers where x > 300 select x).Sum());
    }
}

Now, here is the equivalent code in C# 2.0, taking advantage of my PoorMansLinq class:

C#
using System;
using System.Collections.Generic;
using Compatibility.Linq;

class Program
{
    public static void Main(string[] args)
    {
        string[] words = new string[] {
            "Pies", "Are", "Good", "In", 
            "Lovely", "Apples" };
        // Pies Are Good
        Console.WriteLine(string.Join(" ", Linq(words).Take(3).ToArray()));
        // Apples Are Good In Lovely Pies
        Console.WriteLine(string.Join(" ", Linq(words).Sorted().ToArray()));

        int[] numbers = new int[] { 4, 95, 309, 357, 233, 2 };
        // 1000
        Console.WriteLine(Enumerable.Sum(numbers));
        // 666
        Console.WriteLine(Enumerable.Sum(Linq(numbers)
            .Where(delegate(int x) { return x > 300; })));
    }
    static PoorMansLinq<T> Linq<T>(IEnumerable<T> source)
    {
        return new PoorMansLinq<T>(source);
    }
}

Look at the first WriteLine statement: instead of words.Take(3).ToArray(), it reads Linq(words).Take(3).ToArray(). Linq() is a helper function that simply wraps an IEnumerable object in a PoorMansLinq object; it could have been written new PoorMansLinq<string>(words).Take(3).ToArray() instead.

PoorMansLinq provides most of the LINQ functionality such as Where(), OrderBy(), etc. It forwards all the calls to the static class Enumerable. PoorMansLinq does not include all the functionality of Enumerable:

  • It does not include static methods such as Empty() and Range(first, last) that are not extension methods.
  • It doesn't include AsEnumerable(), which makes no sense without the extension methods feature.
  • It cannot include specializations for specific kinds of T, such as Average<double>() and Sum<int>(), because as far as I know, there is no way to do it with Generics in C# 2.0. Therefore, in order to compute the Sum, Average, Min, or Max of integers, doubles, or decimals, you need to call the method in Enumerable directly.

PoorMansLinq also includes Sorted(), which is a shortcut for OrderBy(x => x) that you see in the second WriteLine statement. In C# 2.0, you would have to write OrderBy(delegate(string x) { return x; }), which is cumbersome.

As I mentioned, you can't Sum numbers using the Linq(numbers).Sum() syntax, so the third WriteLine uses Enumerable.Sum(numbers) instead.

The forth WriteLine demonstrates how a simple query is translated:

C#
from x in numbers
where x > 300
select x

becomes:

C#
Linq(numbers)
    .Where(delegate(int x) { return x > 300; })

I omitted the Select clause, which is not needed in this case. Here's how it looks with the redundant Select clause:

C#
Linq(numbers)
    .Where(delegate(int x) { return x > 300; })
    .Select(delegate(int x) { return x; })

See this article for an introduction to the way C# 3.0 translates LINQ queries to "plain" C# 3.0. Then, the information in this article should be enough to turn it into C# 2.0.

How Did I Do It?

I started by extracting the core LINQ-to-objects code from Mono, which is open source. Then, I wrote PoorMansLinq<T>, a wrapper around IEnumerable<T> that provides the extension methods.

History

  • May 19, 2008: Initial release
  • October 27, 2010: Some functions in PoorMansLinq<T> that returned IEnumerable<T> have been corrected to return PoorMansLinq<T> instead

License

This article, along with any associated source code and files, is licensed under The MIT License