.NN#10: LINQ-Like Functionality In .Net 2.0
With .Net 3.5 coming out soon many developers are hyped about the Language Integrated Query, or LINQ features. In a nutshell LINQ allows you to write queries within your managed code that can run across in-memory objects, databases, XML, and more. You can even execute a query across a set of objects and join against a XML document to produce your results. If you haven’t heard much about LINQ then I highly suggest reading over the link above and then take a peek at what’s possible via the 101 LINQ Samples page. Over the last week I’ve remarked a few times that LINQ would have made an operation we were doing easier (or at least easier to express).
While LINQ is pretty cool stuff, it is available only in .Net 3.5, which not only hasn’t shipped yet (at the time of this writing) but also most likely won’t be immediately adopted in most shops. So what about people who will still be slogging it out in .Net 2.0? Well, it turns out that there is some similar functionality provided via some of the methods of the Array and List classes. While these are no where near as powerful as LINQ (they aren’t actually querying the collection, can’t do joins, etc.) they do provide a more succinct way of interrogating a collection of objects.
For the example below let’s say we have a simple class that represents a customer. The only two properties are a Name and a ZipCode…both string. The code looks like this:
public class Customer
{
public Customer()
{
}
/// <summary>
/// Initializes a new instance of the Customer class.
/// </summary>
/// <param name="name"></param>
/// <param name="zipCode"></param>
public Customer(string name, string zipCode)
{
_name = name;
_zipCode = zipCode;
}
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
private string _zipCode;
public string ZipCode
{
get { return _zipCode; }
set { _zipCode = value; }
}
}
Not too exciting, but sufficient for our needs. Let’s say that we have a requirement that finds the first customer from a list of customers who has a specific zip code. I’ll venture a guess and say most developers would write something like this:
private static Customer FindFirstBurlingtonCustomer(List<Customer> customers)
{
Customer firstCustomerInBurlington = null;
foreach (Customer customer in customers)
{
if (customer.ZipCode == "41005")
{
firstCustomerInBurlington = customer;
break;
}
}
return firstCustomerInBurlington;
}
This is pretty straight forward. It simply loops through the list of Customer objects until it finds the first customer that is from Burlington (has a zip code of 41005). Once it is found it sets a variable and exits the loop.
Now let’s take a look at the Find method of the List class. This method will functionally do the same thing as the code above; it will iterate the List looking for the first occurrence of an object that meets a specific criteria, in our case when the zip code is 41005.
Customer firstBurlingtonCustomerInList = customers.Find(delegate(Customer customer)
{
return customer.ZipCode == "41005";
});
Well, it’s definitely less code, but does it do the same thing? What’s going on here is that the Find Method takes what the documentation calls a Predicate, but basically is a delegate that evaluates some code and returns a boolean value. In the code above the delegate takes a Customer object and returns a boolean result for if the zip code equals “41005”. The find method executes this delegate for each object in the list until it returns true and then returns the instance it found. If it doesn’t find a match it returns a null. This is exactly what the code previous code snippet did.
You can access values outside the delegate as well. Here is an example of the sample above with the value we are searching for pulled out into a local:
string BurlingtonZipCode = "41005";
Customer firstBurlingtonCustomerInList = customers.Find(delegate(Customer customer)
{
return customer.ZipCode == BurlingtonZipCode;
});
Since the predicate is nothing more than a specific type of delegate we can also extract out the predicate to its own method:
//.... somewhere in code
Customer firstBurlingtonCustomerInList = customers.Find(IsBurlingtonCustomer);
//.... more code
private static bool IsBurlingtonCustomer(Customer customer)
{
return customer.ZipCode == "41005";
}
Now we have extracted the code that evaluates our condition into its own method so it can be reused. This can now be used by other methods that require a predicate and also has the added benefit of being able to easily write a unit test around. Very nice. To be fair we could have used this exact same extracted method in our first example, which would have been a good idea for the same reasons: reusability and testability.
Now let’s say we need to find all the Burlington customers in our collection. Using a simple foreach would yield the following code (note it is calling the extracted IsBurlingtonCustomer method from above):
private static List<Customer> FindAllBurlingtonCustomers(List<Customer> customers)
{
List<Customer> burlingtonCustomers = new List<Customer>();
foreach (Customer customer in customers)
{
if (IsBurlingtonCustomer(customer))
{
burlingtonCustomers.Add(customer);
}
}
return burlingtonCustomers;
}
This takes the list of customers to search in, iterates it and adds the customer objects in the list that we are interested in and adds them to a new list. It then returns this new list. With the FindAll method of List this can be simplified to:
private static List<Customer> FindAllBurlingtonCustomers(List<Customer> customers)
{
List<Customer> burlingtonCustomers = new List<Customer>();
foreach (Customer customer in customers)
{
if (IsBurlingtonCustomer(customer))
{
burlingtonCustomers.Add(customer);
}
}
return burlingtonCustomers;
}
In this code I’ve not wrapped it in its own function since it can be in-lined and has the same functionality as the previous example. This is a LOT less code and performs the same function. Under the hood the same operations are actually taking place (iterating the collection), but the Find and FindAll methods provide a syntactical way to reduce code and yield the same result.
Speaking of ways to reduce code…what if we could just reduce the amount of code for a standard foreach? The following line of code will write the results of the previous example to the console:
customersFromBurlington.ForEach(delegate(Customer customer) { Console.WriteLine("Customer: {0}", customer.Name); });
I won’t give examples of the rest of the related type methods but here are some more you can take advantage of:
- Exists: Uses a predicate and returns a true if the predicate evaluated to true for ANY of the objects within the collection.
- FindIndex: Returns the index of the first object in the collection which the predicate evaluates to true.
- FindLast: As you would expect, it finds the last object in the collection which meets the predicate criteria.
- FindLastIndex: You get the idea.
- TrueForAll: This is a neat one which returns a True value if ALL instances in the collection return true for the predicate handed in. So you can imagine writing a method that ensures that all customers in the collection are Burlington Customers.
- ConvertAll: This one allows you to pass a delegate and a type to convert the objects in the collection to. The delegate signature must take in the type of the object in the List and return the type being converted to. What is returned is a List of the new type. Very handy for quick mapping of one type of object to another.
There are similar methods on the Array class that were introduced in the .Net 2.0 Framework that are worth taking a look at ().
NOTE: Again, the methods described above allow for limited interrogation of collections and don’t come close to what you can do with LINQ. It is LINQ-like in that from a collection of objects you can perform operations on some or all of the contents based on delegates. In the case of LINQ the delegates are lambda expressions, which are much more powerful.
Hopefully this is something you can start using now to help simplify your code. As usual, let me know what you think.