Wednesday, April 22, 2015

C# Predicates, some Extention Method goodness and a sprinkling of Generic Methods = DIY LINQ

I was reading an article on .Net Design Patterns in Issue 16 of the Dot Net Curry Magazine where they were describing the Pluggable pattern. This implementation of the pattern used the Predicate type to allow you to pass a predicate expression (a statement that resolves to either true or false) in to a method that totaled up a collection of integers. In the example, a totaling up method used the predicate to determine what values were to be selected from a collection (E.g. only even numbers or numbers greater than five), sum them up and returned the total. This pluggable design allowed you to defer specifying the specific behavior of the totaling up method as you are injecting the value selection criteria into the method. This pattern is very powerful as it allows you to write methods that are concerned with executing a process, whilst not being concerned with the exact details of that process.

This article got me thinking about how LINQ (Language Integrated Query) works under the covers as this process of supplying selection criteria is what is happening here too. After chatting to my brother about his blog post where he set himself the challenge of writing his own implementation of Javascript Promises (it’s very cool--check it out here), this motivated me into setting my own mini challenge: Build my own version of the LINQ Where statement.

Like my brother, I decided to set out some constraints:

  • It should work using a fluent syntax.
  • It should accept a lambda predicate statement as the selector.
  • It should work with all types (not just integers or strings).
Effectively, I was wanting to create something which mirrored LINQ’s Where statement's API as closely as possible.
 

I started by breaking down the task into smaller parts. First I created a “WhereMine” method which accepted an IEnumerable<int> and a Predicate<int> that returned an IEnumerable<int> containing the integers which matched the supplied predicate. This can be seen below:

public static IEnumerable<int> WhereMine(IEnumerable<int> values, 
     Predicate<int> predicate)
{
 IList<int> matchedValues = new List<int>();

 foreach(var value in values)
 {
  if(predicate(value))                
   matchedValues.Add(value);                
 }

 return matchedValues;
}

This step allowed me to verify that the predicate expression was working as I had expected it to by only adding a value to the matchedValues collection if the predicate expression was true.

The WhereMine method was called using the following syntax (Please note, DisplayToConsole() is a helper extension method I wrote to write the contents of an enumerable to the console):


IEnumerable<int> numbers = new[] { 1, 2, 3, 4, 5 };
Console.WriteLine("Numbers:");
numbers.DisplayToConsole();

//Predicate Method
Console.WriteLine("Predicate Method");
Console.WriteLine("Only Even:");
WhereMine(numbers, x => x % 2 == 0).DisplayToConsole();
Console.WriteLine("Greater than 2:");
WhereMine(numbers, x => x > 2).DisplayToConsole();

This generated the following output:


So far so good. Next I created an extension method that extended IEnumerable<int> to help provide a similar fluent syntax to LINQ’s Where API:

public static IEnumerable<int> WhereMine(this IEnumerable<int> values, 
 Predicate<int> predicate)
{
 IList<int> matchedValues = new List<int>();

 foreach(var value in values)
 {
  if (predicate(value))
   matchedValues.Add(value);
 }

 return matchedValues;
}

If you are not familiar with creating your own extension methods, you can see this is accomplished by preceding the type you are extending with the "this" keyword (highlighted above in yellow).

This update allowed me to use my WhereMine filter method using a fluent syntax:

IEnumerable<int> numbers = new[] { 1, 2, 3, 4, 5 };
Console.WriteLine("Numbers:");
numbers.DisplayToConsole();

//Extension Method
Console.WriteLine("Extension Method");
Console.WriteLine("Only odd:");
numbers.WhereMine(x => x % 2 == 1).DisplayToConsole();
Console.WriteLine("Greater than 3 but less than 5");
numbers.WhereMine(x => x > 3 && x < 5).DisplayToConsole();

This generated the following output:


Almost there. The last step was to update it so it worked with all types instead of being limited to a single type as my current implementation was. I achieved this by making my WhereMine extension method a generic extension method using the generic template markers highlighted in yellow below:

public static IEnumerable<T> WhereMine<T>(this IEnumerable<T> values, 
     Predicate<T> predicate)
{
 IList<T> matchedValues = new List<T>();

 foreach(var value in values)
 {
  if (predicate(value))
   matchedValues.Add(value);
 }

 return matchedValues;
}

By making it a generic extension method it was now possible to use the WhereMine extension method to filter collections of different types:

//Generic Extension Method
Console.WriteLine("Generic Extension Method");
Console.WriteLine("Numbers doubles:");
IEnumerable<double> numbersDouble = 
     new[] { 1.5d, 5.6d, 9.9d, 10.8d, 20d };
numbersDouble.DisplayToConsole();
Console.WriteLine("Less than 10");
numbersDouble.WhereMine(x => x < 10).DisplayToConsole();

IEnumerable<string> names = 
     new[] { "Jane", "Steve", "Jack", "Betty", "Bill", "Jenny", "Joseph" };
Console.WriteLine("Names:");
names.DisplayToConsole();
Console.WriteLine("Names that start with 'J':");
names.WhereMine(x => x.StartsWith("J")).DisplayToConsole();
Console.WriteLine("Names that are 5 characters in length:");
names.WhereMine(x => x.Length == 5).DisplayToConsole();
Console.WriteLine("Names that start with 'J' and are longer than 4 chars");
names.WhereMine(x => x.Length > 4)
     .WhereMine(x => x.StartsWith("J"))
     .DisplayToConsole();
// Or could be written as
//names.WhereMine(x => x.Length > 4 && x.StartsWith("J")).DisplayToConsole();

This demonstration uses the same WhereMine extension method to filter an enumerable of doubles and an enumerable of strings generating the following output:


By combining a number of the language features of C#, I was able to achieve my end goal of creating my own filter method like the LINQ Where statement with a similar API. I think of this activity as exploratory coding (or sharpening the saw)--where you are trying to build something with the intention of learning more about the language / framework you are using and not necessarily using the end product. I find this learning process a lot more fun and insightful than simply reading about something in a book or on a website. Building something helps solidify what you have learnt and can identify any misunderstandings or gaps in your knowledge.

You can find a project which contains the above source code here.


Happy exploratory coding!

No comments:

Post a Comment