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).
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!