Projection Operators and extension methods
This installment give you a taste of extension methods. We’ll look at Partitioning Operations, which enable you to retrieve parts of a collection based on an items position in the collection. This functionality is defined using extension methods, which means that the methods can be used with any collection. Or, more correctly, these methods can be used with any class that implements IEnumerable<T>.
As I’ve been doing, we’ll walk through the pertinent sample code and see what new features these next samples show.
The first few samples show Take, which limits the number of returned values. These two methods show how to retrieve the first 3 elements to an array, and a nested query:
public void Linq20() {
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; var first3Numbers = numbers.Take(3);
Console.WriteLine("First 3 numbers:");
foreach (var n in first3Numbers) {
Console.WriteLine(n);
}
}public void Linq21() {
List<Customer> customers = GetCustomerList(); var first3WAOrders = (
from c in customers,
o in c.Orders
where c.Region == "WA"
select new {c.CustomerID, o.OrderID, o.OrderDate} )
.Take(3);
Console.WriteLine("First 3 orders in WA:");
foreach (var order in first3WAOrders) {
ObjectDumper.Write(order);
}
}The important lesson of these two samples is not that you can retrieve a small number of items, but rather that by writing an extension method, you can create functionality that can be reused in a large number of ways. The Take method is in Sequence.cs, which is delivered with the LINQ preview:
public static IEnumerable<T> Take<T>
(this IEnumerable<T> source, int count) {
if (count > 0) {
foreach (T element in source) {
yield return element;
if (--count == 0) break;
}
}
}
Retrieving N items from a collection is incredibly simple. So, let’s skip forward and look at the two samples that use TakeWhile. TakeWhile continues to return elements as long as a test condition is true. For example, this sample returns the numbers in the array as long as all values encountered are less than 6:
public void Linq24() {
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var firstNumbersLessThan6 = numbers.TakeWhile(n => n < 6);
Console.WriteLine("First numbers less than 6:");
foreach (var n in firstNumbersLessThan6) {
Console.WriteLine(n);
}
}The output is: 5,4,1,3. The 9 doesn’t pass the test, and therefore terminates the output. A more complicated example shows how to use a more interesting condition. The copy and paste demons were in play here, because the variable name (firstSmallNumbers) below doesn’t describe what it’s doing really well. This method will continue to return elements as long as the number returned is greater than or equal to its position.
public void Linq25() {
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; var firstSmallNumbers = numbers.TakeWhile((n, index) => n >= index);
Console.WriteLine("First numbers not less than their position:");
foreach (var n in firstSmallNumbers) {
Console.WriteLine(n);
}
}The output is 5,4. The 1 is less then its position in the array (2), so the iteration stops. As with Take(), TakeWhile() is an extension method that is delivered in Sequence.cs. There are two versions of TakeWhile(). The first examines some property of an element. The second examines some property of the element, and its position in the collection:
public static IEnumerable<T> TakeWhile<T>
(this IEnumerable<T> source, Func<T, bool> predicate) {
foreach (T element in source) {
if (!predicate(element)) break;
yield return element;
}
}
public static IEnumerable<T> TakeWhile<T>
(this IEnumerable<T> source, Func<T, int, bool> predicate) {
int index = 0;
foreach (T element in source) {
if (!predicate(element, index)) break;
yield return element;
index++;
}
}
Take and TakeWhile provide the means for you to examine the first part of a collection. Skip and SkipWhile are the way you access the latter parts of a collection. Skip() jumps past the first N elements in a collection. (I’m just showing some excerpts from the samples now, rather than the entire collection.)
// Skip the first 4 numbers in an array:
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var allButFirst4Numbers = numbers.Skip(4);
// Skip the first 2 elements in the collection returned from a query:
List<Customer> customers = GetCustomerList();
var waOrders =
from c in customers,
o in c.Orders
where c.Region == "WA"
select new {c.CustomerID, o.OrderID, o.OrderDate};
var allButFirst2Orders = waOrders.Skip(2);
Just like Take(), Skip() is an extension method. By creating an extension method, you can apply Skip() to any type that implements IEnumerable<T>:
public static IEnumerable<T> Skip<T>
(this IEnumerable<T> source, int count) {
using (IEnumerator<T> e = source.GetEnumerator()) {
while (count > 0 && e.MoveNext()) count--;
if (count <= 0) {
while (e.MoveNext()) yield return e.Current;
}
}
}
Finally, in the same way that Take() is complemented by TakeWhile(), Skip() is complemented by SkipWhile():
// Skip until the returned value is divisible by 3:
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var allButFirst3Numbers = numbers.SkipWhile(n => n % 3 != 0);
// allButFirst3Numbers contains 3,9,8,6,7,2,0
// Skip until the number is less than its position in the array:
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var laterNumbers = numbers.SkipWhile((n, index) => n >= index);
// laterNumbers contains 1,3,9,8,6,7,2,0
One more time, repeat after me: This is an extension method so SkipWhile() can be used with any type that implements IEnumerable<T>. (And, like TakeWhile(), there are two versions. The first examines the element, and the second the element and its position in the array
public static IEnumerable<T> SkipWhile<T>
(this IEnumerable<T> source, Func<T, bool> predicate) {
bool yielding = false;
foreach (T element in source) {
if (!yielding && !predicate(element)) yielding = true;
if (yielding) yield return element;
}
}
public static IEnumerable<T> SkipWhile<T>
(this IEnumerable<T> source, Func<T, int, bool> predicate) {
int index = 0;
bool yielding = false;
foreach (T element in source) {
if (!yielding && !predicate(element, index)) yielding = true;
if (yielding) yield return element;
index++;
}
}
Well, those are the sample partition operators. Later this week, we’ll move on with more samples.
Part 1
The general query syntax
Part 2
The one where I discuss Object and Collection Initializers
Part 3
The one where I finish restriction operators
Part 4
Beginning to discuss Projections
Part 5
Anonymous types and projections
Part 6
Discussing indexed, filtered, and compound queries
Part 7
Finishing up the projection items