Karol Szmaj

Karol Szmaj


Thoughts, stories and ideas about programming.

Karol Szmaj
Author

CTO at Whalla Labs, .Net, iOS Developer.

Share


Tags


Twitter


Extending IEnumerable<T> - InteractiveExtensions by example

Karol SzmajKarol Szmaj

How many .Net developers use LINQ nowadays? I think a lot, because it's the best choice for quering sequences or services, e.g. LINQToObjects, LINQToTwitter.
What is Interactive Extensions? It's a set of additional LINQ to Objects query operators based on the Reactive Extensions.

In this post I will describe all the API available from the System.Interactive assembly.

Instalation

To install InteractiveExtensions we will need only package name Ix-Main that is available on nuget.org.

To install simply paste code below on Package Manager Console

Install-Package Ix-Main

IEnumerable

The IEnumerable interface represents a pull based collection. This interface exposes the enumerator, which supports a simple iteration over a collection.

Methods

Buffer - generates a sequence of non-overlapping adjacent buffers over the source sequence.

private static void Main(string[] args)
{
    var numbers = Enumerable.Range(0, 10);
    var bufferedNumbers = numbers.Buffer(3);
    bufferedNumbers.ForEach((bufferedElement) =>
    {
        Console.Write("Sequence: ");
        bufferedElement.ForEach((value) => Console.Write("{0}, ", value));
        Console.WriteLine();
    });
}

Console log:
Sequence: 0, 1, 2,
Sequence: 3, 4, 5,
Sequence: 6, 7, 8,
Sequence: 9,

So the input sequence was divided into 4 sequences with max 3 elements inside each collection.

Case - returns a sequence from a dictionary based on the result of evaluating a selector function, also specifying a default sequence.

var dictionaryFilter = new Dictionary<int, IEnumerable<string>>()
{
    {42, new String[] {"4","2"}}
};
var result = EnumerableEx.Case(() => 42, dictionaryFilter, new String[] {"unknown"});
result.ForEach((s) => Console.Write(s)); // prints 42

Catch - creates a sequence by concatenating source sequences until a source sequence completes successfully.

static void Main(string[] args)
{
    //s1:      1, 2, Ex
    //s2:            3, 4, 5
    //Result:  1, 2, 3, 4, 5

    var s1 = CreateErrorSequence();
    var s2 = Enumerable.Range(3, 3);
    var result = s1.Catch(s2);
    result.ForEach(Console.Write); //prints 12345
}

private static IEnumerable<int> CreateErrorSequence()
{
    yield return 1;
    yield return 2;
    throw new ArgumentOutOfRangeException();
}

Contact - concatenates two sequences.

static void Main(string[] args)
{
    var s1 = Enumerable.Range(0, 50);
    var s2 = Enumerable.Range(50, 50);
    var result = s1.Concat(s2);
    result.ForEach(Console.Write); //prints 0...99
}

Create - creates an lazy enumerable sequence based on an asynchronous method that provides a yielder.

static void Main(string[] args)
{
    var lazyEnumerable = EnumerableEx.Create<int>(async yielder =>
    {
        var indexer = 0;
        while (indexer < 10)
        {
            await yielder.Return(indexer++);
        }
    });
    lazyEnumerable.ForEach(Console.Write); //prints 0...9
}

Deffer - creates an enumerable sequence based on an enumerable factory function.

static void Main(string[] args)
{
    var enumerable = EnumerableEx.Defer(() => Enumerable.Range(0, 5));
    enumerable.ForEach(Console.Write); //Prints 01234
}

DistinctUntilChanged - returns consecutive distinct elements by using the default equality comparer to compare values.

private static void Main(string[] args)
{
    var numbers = new int[] {1, 1, 2, 3, 1, 1, 3, 2, 2, 1};
    numbers.Distinct().ForEach(Console.Write);
    Console.WriteLine();
    numbers.DistinctUntilChanged().ForEach(Console.Write);
}

Distinct method produces sequence that contains: 1, 2, 3.
DistinctUntilChanged returns: 1, 2, 3, 1, 3, 2, 1.

Do - lazily invokes an action for each value in the sequence, and executes an action for successful termination or exception.

private static void Main(string[] args)
{
    var numbers = new int[] { 1, 1, 2, 3, 1, 1, 3, 2, 2, 1 };
    var numbersWithAction = numbers.Do(Console.Write, () => Console.WriteLine("\nExecution completed"));
    numbersWithAction.ToList();
}

Console log:
1123113221
Execution completed

Expand - expands the sequence by recursively applying a selector function.

private static void Main(string[] args)
{
    var number = new int[] {1};
    number.Expand(value => new int[] {value*2})
        .Take(10)
        .ForEach(x => Console.Write("{0}, ", x));
}

Console log:
1, 2, 4, 8, 16, 32, 64, 128, 256, 512

Finally - creates a sequence whose termination or disposal of an enumerator causes a finally action to be executed.

private static void Main(string[] args)
{
    var number = new int[] {1};
    number.Take(10)
        .Finally(() => Console.Write("Enumeration interrputed or disposed."))
        .ToArray();
}

Console log:
Enumeration interrputed or disposed.

For - Generates a sequence by enumerating a source sequence, mapping its elements on result sequences, and concatenating those sequences.

private static void Main(string[] args)
{
    var numbers = Enumerable.Range(0, 10);
    EnumerableEx.For(numbers, value => new int[] {value*value})
        .ForEach(x => Console.Write("{0},", x));
}

Console log:
0,1,4,9,16,25,36,49,64,81,

ForEach - enumerates the sequence and invokes the given action for each value in the sequence.

private static void Main(string[] args)
{
    var numbers = Enumerable.Range(0, 10);
    numbers.ForEach(x => Console.Write("{0},", x)); //ForEach returns void
}

Console log:
0,1,2,3,4,5,6,7,8,9,

Generate - generates a sequence by mimicking a for loop.

EnumerableEx.Generate(0,             //init state
                x => x < 5,          //condition
                x => x + 1,          //iteration
                x => x*x)            //result selector
                .ForEach(x => Console.Write("{0},", x));

Console log:
0,1,4,9,16,

If - returns an enumerable sequence based on the evaluation result of the given condition.

private static void Main(string[] args)
{
    var numbers = Enumerable.Range(0, 10);
    EnumerableEx.If(
        () => numbers.Contains(10), //condition
        numbers,                    // return numbers if condition == true
        Enumerable.Range(0, 12)     //return Enumerable.Range(0, 12) if condition == false
        ).ForEach(x=> Console.Write("{0}, ",x));
}

Console log:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,

IgnoreElements - ignores all elements in the source sequence.

private static void Main(string[] args)
{
    var numbers = Enumerable.Range(0, 10);
    var hasElements = numbers.IgnoreElements().Any();
    Console.Write("Sequence has elements? {0}", hasElements);
}

Console log:
Sequence has elements? False

IsEmpty - determines whether an enumerable sequence is empty.

private static void Main(string[] args)
{
    var numbers = IntFactory();
    var isEmpty = numbers.IsEmpty();
    Console.Write("Sequence is empty - {0}", isEmpty);
}

private static IEnumerable<int> IntFactory()
{
    yield return 1;
    yield return 2; // this won't be executed
}

Console log:
Sequence is empty - false

Memomize - creates a buffer with a view over the source sequence, causing a specified number of enumerators to obtain access to all of the sequence's elements without causing multiple enumerations over the source.

private static void Main(string[] args)
{
    var numbers = Enumerable.Range(0, 10);
    EnumerableEx.For(
        numbers.Memoize(5),
        value => new int[] {value*2}
            ).ForEach(x => Console.Write("{0}, ", x));
}

Console log:
0, 2, 4, 6, 8, 10, 12, 14, 16, 18,

OnErrorResumeNext - creates a sequence that concatenates both given sequences, regardless of whether an error occurs.

private static void Main(string[] args)
{
    var numbers = CreateErrorSequence();
    numbers.OnErrorResumeNext(Enumerable.Range(0, 10))
        .ForEach(x => Console.Write("{0}, ", x));
}
private static IEnumerable<int> CreateErrorSequence()
{
    yield return 1;
    yield return 2;
    throw new InvalidOperationException();
}

Console log:
1, 2, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,

Publish - creates a buffer with a view over the source sequence, causing each enumerator to obtain access to the remainder of the sequence from the current index in the buffer.

private static void Main(string[] args)
{
    var numbers = CreateSequence().Publish();
    var enumerator1 = numbers.GetEnumerator();
    var enumerator2 = numbers.GetEnumerator();
    enumerator1.MoveNext();
    Console.WriteLine(enumerator1.Current);
    enumerator2.MoveNext();
    enumerator2.MoveNext();
    Console.WriteLine(enumerator2.Current);
}
private static IEnumerable<int> CreateSequence()
{
    yield return 1; //each uield will be executed only once despite enumerators count
    yield return 2;
    yield return 3;
    yield return 4;
}

Console log:
1
2

Repeat - repeats and concatenates the source sequence the given number of times.

private static void Main(string[] args)
{
    var numbers = CreateSequence().Repeat(2);
    numbers.ForEach(x=>Console.Write("{0}, ", x));
}
private static IEnumerable<int> CreateSequence()
{
    yield return 1; 
    yield return 2;
    yield return 3;
    yield return 4;
}

Console log:
1, 2, 3, 4, 1, 2, 3, 4,

Important note: If you use Publish before Retry your sequence will be not repeated!

Retry - creates a sequence that retries enumerating the source sequence as long as an error occurs, with the specified maximum number of retries.

private static bool canCauseError = true;
private static void Main(string[] args)
{
    var numbers = CreateErrorSequence().Retry(2);
    numbers.ForEach(x => Console.Write("{0}, ", x));
}
private static IEnumerable<int> CreateErrorSequence()
{
    yield return 1;
    yield return 2;
    yield return 3;
    yield return 4;
    if (canCauseError)
    {
        canCauseError = false;
        throw new InvalidOperationException();
    }
    else
    {
        yield return 5;
    }
}

Console log:
1, 2, 3, 4, 1, 2, 3, 4, 5,

Return - returns a sequence with a single element.

private static void Main(string[] args)
{
    var numbers = EnumerableEx.Return(1);
    numbers.ForEach(Console.Write);
}

Console log:
1

Scan - generates a sequence of accumulated values by scanning the source sequence and applying an accumulator function.

private static void Main(string[] args)
{
    var numbers = Enumerable.Range(0, 5).Scan((acc, x) =>
    {
        Console.WriteLine("Acc = {0}, x = {1}", acc, x);
        return acc + x;
    });
    var numbers2 = numbers.ToArray();
    Console.Write("Array result:");
    numbers2.ForEach(x => Console.Write(" {0} ", x));
}

Console log:
Acc = 0, x = 1
Acc = 1, x = 2
Acc = 3, x = 3
Acc = 6, x = 4
Array result: 1 3 6 10

SkipLast - bypasses a specified number of contiguous elements from the end of the sequence and returns the remaining elements.

private static void Main(string[] args)
{
    var sequence1 = CreateSequence().SkipLast(2);
    sequence1.ForEach(x => Console.Write("{0} ", x));
}

Console log:
1 2

StartsWith - returns the source sequence prefixed with the specified value.

private static void Main(string[] args)
{
    var sequence1 = Enumerable.Range(0, 10).StartWith(4, 8);
    sequence1.ForEach(x => Console.Write("{0} ", x));
}

Console log:
4 8 0 1 2 3 4 5 6 7 8 9

TakeLast - returns a specified number of contiguous elements from the end of the sequence.

private static void Main(string[] args)
{
    var sequence1 = Enumerable.Range(0, 10).TakeLast(5);
    sequence1.ForEach(x => Console.Write("{0} ", x));
}

Console log:
5 6 7 8 9

Throw - returns a sequence that throws an exception upon enumeration. Useful for testing purposes.

private static void Main(string[] args)
{
    var errorSequence = EnumerableEx.Throw<int>(new InvalidOperationException("Simulating error sequence"));
    errorSequence = errorSequence.OnErrorResumeNext(EnumerableEx.Return(1));
    errorSequence.ForEach(x => Console.Write("{0} ", x));
}

Console log:
1

While - generates an enumerable sequence by repeating a source sequence as long as the given loop condition holds.

private static void Main(string[] args)
{
    var ctr = 10;
    var sequence = EnumerableEx.While(() => ctr > 0, EnumerableEx.Defer(() => new[] {ctr})
        .Do(_ => ctr--));
    sequence.ForEach(x => Console.Write("{0} ", x));
}

Console log:
10 9 8 7 6 5 4 3 2 1

Summary

Interactive Extensions for sure is worth trying. Framework provides many useful extension methods like ForEach or DistinctUntilChanged. Well done Reactive Extensions operators came to pull based sequences and I recommend to use it widely in your project.

More information about IX can be found at channel9: https://channel9.msdn.com/Shows/Going+Deep/Bart-De-Smet-Interactive-Extensions-Ix

Karol Szmaj
Author

Karol Szmaj

CTO at Whalla Labs, .Net, iOS Developer.

Comments