The Between operator is a sample Sequence operator that returns elements after a StartPredicate has been satisfied, until an End Predicate is satisfied. It is written as a sample and the operator could be implemented in alternative ways using the
SkipWhile operator and the
TakeWhile operators using a normal query.
The only information we have on the source sequence is that it is IEnumerable
(being that is the type our extension method is targeting), so we know we can foreach over the source elements, or manually use our own iteration block.
The code behind this operator shows remarkable similarity to the Sequence.cs source code supplied by Microsoft as part of the MAY 2006 LINQ CTP. This is intentional, the patterns shown in the Standard Query Operators should be the basis for all of our operators to ensure a consistent developer experience when using them. We should throw similar exceptions, and use the delegate prorotypes that all the other operators use.
Back to Writing Operators
EditThe Code
using System;
using System.Collections.Generic;
using System.Text;
using System.Query;
namespace Aspiring.Query
{
public static class BetweenExtensions
{
public static IEnumerable<T> Between<T>(
this IEnumerable<T> source,
Func<T, bool> endPredicate) {
if (source == null) throw Error.ArgumentNull("source");
if (endPredicate == null) throw Error.ArgumentNull("endPredicate");
return BetweenIterator<T>(source, b => true, endPredicate);
}
public static IEnumerable<T> Between<T>(
this IEnumerable<T> source,
Func<T, bool> startPredicate,
Func<T, bool> endPredicate) {
if (source == null) throw Error.ArgumentNull("source");
if (startPredicate == null) throw Error.ArgumentNull("startPredicate");
if (endPredicate == null) throw Error.ArgumentNull("endPredicate");
return BetweenIterator<T>(source, startPredicate, endPredicate);
}
static IEnumerable<T> BetweenIterator<T>(
IEnumerable<T> source,
Func<T, bool> startPredicate,
Func<T, bool> endPredicate) {
bool foundStart = false;
foreach (T element in source) {
if (startPredicate(element))
foundStart = true;
if (foundStart)
if (!endPredicate(element))
yield return element;
else
yield break;
}
}
class Error {
internal static Exception ArgumentNull(string paramName) {
return new ArgumentNullException(paramName);
}
}
}
}
EditThe Unit Tests
using System;
using System.Collections.Generic;
using System.Text;
using System.Query;
using NUnit.Framework;
using Aspiring.Query;
namespace Aspiring.Query.UnitTests
{
[TestFixture]
public class BetweenTests
{
[Test]
[ExpectedException("System.ArgumentNullException")]
public void BetweenEndPredicateNullSourceTest() {
int[] values = null;
int[] result = values.Between(end => true).ToArray<int>();
}
[Test]
[ExpectedException("System.ArgumentNullException")]
public void BetweenEndPredicateNullPredicateTest() {
int[] values = null;
int[] result = values.Between(null).ToArray<int>();
}
[Test]
[ExpectedException("System.ArgumentNullException")]
public void BetweenStartAndEndPredicateNullSourceTest() {
int[] values = null;
int[] result = values.Between(start => true, end => true).ToArray<int>();
}
[Test]
[ExpectedException("System.ArgumentNullException")]
public void BetweenStartAndEndPredicateNullPredicateTest() {
int[] values = null;
int[] result = values.Between(null, null).ToArray<int>();
}
[Test]
public void BetweenStartAndEndIntTest() {
int[] values = Sequence.Range(0,20).ToArray<int>();
int[] result = values.Between(start => start > 4, end => end > 9).ToArray<int>();
Assert.AreEqual(5,result.Count(), "incorrect number returned");
for (int i = 0; i < result.Count(); i++)
Assert.AreEqual(i+5, result[i]);
}
}
}