Wednesday, May 19, 2010

Using Extension methods on Lists to make it fluent

Since C# 3.0 you can write extension methods on any class that you want, even if you don’t have the source code of the class. In other words you can extend a class with your own methods.

Extension methods can only be declared in static classes as static methods.

Extensions on List<T> classes can be very handy and make your code more readable and fluent.
Suppose you have this mixed Animal list with Dogs and Cats from this blogpost.

List<Animal> animalList = new List<Animal>(); 
animalList.Add(new Dog("Dog1", ConsoleColor.Red));
animalList.Add(new Dog("Dog2", ConsoleColor.Green));
animalList.Add(new Dog("Dog3", ConsoleColor.Red));
animalList.Add(new Cat("Cat1", ConsoleColor.Black));
animalList.Add(new Cat("Cat2", ConsoleColor.Black));
animalList.Add(new Cat("Cat3", ConsoleColor.Black));

(Never mind the ConsoleColor for the animal ;-) )

Suppose we want all the red dogs from the animalList, we could write our code like:


//Get the Red docs

List<Dog> redDogList = animalList.OfType<Dog>().Where(n => n.Color == ConsoleColor.Red).ToList();

This works, however everytime we want to get some colored animal we would get the same type of code. We would probably write some static methods to avoid this :


 List<Dog> redDogList = ListUtils.GetTheDogs(animalList);


Even better is to make this static extension methods so that the functionality looks to be encapsulated by the list its self. We can then also make the method names more readable, for instant not “GetTheDog”, but “WhichAreDog”.


For example:
public static List<Animal> WhichAreDog(this List<Animal> animalList)
{
  return animalList.OfType<Dog>().Cast<Animal>().ToList();
}



public static
List<Animal> HavingColor(this List<Animal> animalList, ConsoleColor color)
{
  return animalList.Where(n => n.Color == color).ToList();
}

The “this” before the first parameter of the method indicates that this is an extension method on this parameter, in this case the list with animals.


Now getting the red dogs looks like this:


List<Animal> allRedDogs = 
animalList.WhichAreDog().HavingColor(ConsoleColor.Red);

Much more readable and fluent!

Monday, May 03, 2010

Exploring LINQ #3

LINQ keeps amazing me in its power and beauty. So much that for each problem I have to solve (coding problem that is)  I find myself wondering can this be done with LINQ, and how.

I am working on a rewrite of an old application to C#. For some reason the original designers had put some ‘model’ data into a none relational database. Instead of making a good relational data model the data has been put as follows (simplified record from the database) :

Option1 = “1;2;3”
Option2 = “A;B”
Value = 1

In other words foreach possible combination of option1 and option2 there is a value. (1-A-1, 1-B-1, 2-A-1 etc.) I would prefer a relational set up, but to honor the past this will have to stay that way. For use in our program it  is required to have all possible combinations into a single flat list which we then can query with LINQ.
Of course it easy to construct some code that would knock this down into a flat list though I don’t think any will be as elegant and as fast to produce as this LINQified solution.

Suppose we read the raw data from the database into a list with raw objects looking like this:

List<Raw> rawList = new List<Raw>();
rawList.Add(new Raw() { Option1 = "1;2;3",
Option2 = "A", Value = 1 });
rawList.Add(new Raw() { Option1 = "4;5",
Option2 = "A;B", Value = 2 });
rawList.Add(new Raw() { Option1 = "1;2;3",
Option2 = "C", Value = 3 });

Using LINQ it is easy to combine the original raw list with the “;” splitted list (arrays in fact) from the fields Option1 and Option2. From that a flat list can be constructed which holds all the combinations:


List<Raw> flatList = (from n in rawList
from opt1 in n.Option1.Split(';')
from opt2 in n.Option2.Split(';')
select new Raw
{
Option1 = opt1,
Option2 = opt2,
Value = n.Value
}).ToList();
flatList.ForEach((n) => Console.WriteLine(n.Option1 +
"-" + n.Option2 +
"-" + n.Value.ToString()));

Resulting in a ‘flat’ list:


1-A-1
2-A-1
3-A-1
4-A-2
4-B-2
5-A-2
5-B-2
1-C-3
2-C-3
3-C-3


Querying this list is now child’s play:

int Result = (from n in flatList
              where (n.Option1 == "4") && (n.Option2 == "B")
              select n.Value).FirstOrDefault();