<file_sep>using System;
using System.Linq;
using System.Collections.Generic;

namespace Chains
{
    public static partial class Chains
    {
        /// <summary>
        /// Yields the values in an <see cref="IEnumerable{TSource}"/>
        /// infinitely, starting over again if the end is reached.
        /// </summary>
        /// <typeparam name="TSource">
        /// The type of the items in the source enumerable.
        /// </typeparam>
        /// <param name="source">The source enumerable to operate on.</param>
        /// <exception cref="ArgumentNullException">
        /// The source is null.
        /// </exception>
        /// <returns>
        /// An infinitely repeating version of the source enumerable.
        /// </returns>
        public static IEnumerable<TSource> Cycle<TSource>(
            this IEnumerable<TSource> source)
        {
            source.EnsureNotNull(nameof(source));

            if (!source.Any()) yield break;

            while (true)
                foreach (var item in source)
                    yield return item;
        }
    }
}
<file_sep>using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Chains;

namespace Chains.Test
{
    [TestClass]
    public class ScanTests
    {
        public const string ExpectedEmptySequence = "Exected sequence to be empty.";
        public const string ExpectedNonEmptySequence = "Exected sequence not to be empty.";
        public const string ExpectedSizeMismatch = "Sequence sizes differed.";
        public const string ExpectedElementMismatch = "Sequence element did not match expectation.";

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Scan2_Should_ThrowIfSourceIsNull()
        {
            var sequence = (IEnumerable<object>)null;

            var actual = sequence
                .Scan((x, y) => x)
                .ToList();
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Scan2_Should_ThrowIfAccumulatorIsNull()
        {
            var sequence = Enumerable.Range(1, 3);

            var actual = sequence
                .Scan((Func<int, int, int>)null)
                .ToList();
        }

        [TestMethod]
        public void Scan2_Should_ProduceAnEmptySequenceIfSourceIsEmpty()
        {
            var sequence = Enumerable.Empty<object>();

            var actual = sequence
                .Scan((x, y) => x);

            Assert.IsFalse(actual.Any(), ExpectedEmptySequence);
        }

        [TestMethod]
        public void Scan2_Should_ProduceTheExpectedElements()
        {
            var sequence = new[]{ 1, 2, 3 };
            var expected = new[]{ 1, 3, 6 };

            var actual = sequence
                .Scan((x, y) => x + y);

            Assert.AreEqual(expected.Length, actual.Count(), ExpectedSizeMismatch);
            Assert.IsTrue(actual.SequenceEqual(expected), ExpectedElementMismatch);
        }


        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Scan3_Should_ThrowIfSourceIsNull()
        {
            var sequence = (IEnumerable<object>)null;

            var actual = sequence
                .Scan(new object(), (x, y) => x)
                .ToList();
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Scan3_Should_ThrowIfAccumulatorIsNull()
        {
            var sequence = Enumerable.Range(1, 3);

            var actual = sequence
                .Scan(0, (Func<int, int, int>)null)
                .ToList();
        }

        [TestMethod]
        public void Scan3_Should_YieldTheSeedValueIfSourceIsEmpty()
        {
            var sequence = Enumerable.Empty<int>();
            var seed = 1;

            var actual = sequence
                .Scan(seed, (x, y) => x);

            Assert.AreEqual(1, actual.Count(), ExpectedNonEmptySequence);
            Assert.AreEqual(seed, actual.First(), ExpectedElementMismatch);
        }

        [TestMethod]
        public void Scan3_Should_ProduceTheExpectedElements()
        {
            var sequence = new[]{ 1, 2, 3 };
            var seed = 0;
            var expected = new[]{ seed, 1, 3, 6 };

            var actual = sequence
                .Scan(seed, (x, y) => x + y);

            Assert.AreEqual(expected.Length, actual.Count(), ExpectedSizeMismatch);
            Assert.IsTrue(actual.SequenceEqual(expected), ExpectedElementMismatch);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Scan4_Should_ThrowIfSourceIsNull()
        {
            var sequence = (IEnumerable<object>)null;

            var actual = sequence
                .Scan(new object(), (x, y) => x, x => x)
                .ToList();
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Scan4_Should_ThrowIfAccumulatorIsNull()
        {
            var sequence = Enumerable.Range(1, 3);

            var actual = sequence
                .Scan(0, (Func<int, int, int>)null, x => x)
                .ToList();
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Scan4_Should_ThrowIfTransformerIsNull()
        {
            var sequence = Enumerable.Range(1, 3);

            var actual = sequence
                .Scan(0, (x, y) => x, (Func<int, int>)null)
                .ToList();
        }

        [TestMethod]
        public void Scan4_Should_YieldTheTransformedSeedValueIfSourceIsEmpty()
        {
            var sequence = Enumerable.Empty<int>();
            var seed = 1;
            var expected = 3;

            var actual = sequence
                .Scan(seed, (x, y) => x, x => expected);

            Assert.AreEqual(1, actual.Count(), ExpectedNonEmptySequence);
            Assert.AreEqual(expected, actual.First(), ExpectedElementMismatch);
        }

        [TestMethod]
        public void Scan4_Should_ProduceTheExpectedElements()
        {
            var sequence = new[]{ 1, 2, 3 };
            var seed = 0;
            var offset = 2;
            var expected = new[]{ offset, 3, 5, 8 };

            var actual = sequence
                .Scan(seed, (x, y) => x + y, x => x + offset);

            Assert.AreEqual(expected.Length, actual.Count(), ExpectedSizeMismatch);
            Assert.IsTrue(actual.SequenceEqual(expected), ExpectedElementMismatch);
        }
    }
}
<file_sep>using System;
using System.Collections.Generic;
using System.Linq;

namespace Chains
{
    public static partial class Chains
    {
        /// <summary>
        /// Compares the values in two <see cref="IEnumerable{TSource}"/>
        /// instances, element-by-element.
        /// </summary>
        /// <typeparam name="TSource">
        /// The type of the items in the source enumerables.
        /// </typeparam>
        /// <param name="source">The source enumerable to operate on.</param>
        /// <param name="other">The enumerable to compare to.</param>
        /// <exception cref="ArgumentNullException">
        /// The source is null.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// The other is null.
        /// </exception>
        /// <returns>
        /// The first non-zero value of element-by-element comparison, or the
        /// value -1 if the first sequence is shorter, the value +1 if the
        /// second sequence is shorter, or finally the value 0.
        /// </returns>
        public static int CompareWith<TSource>(
            this IEnumerable<TSource> source,
            IEnumerable<TSource> other)
            where TSource : IComparable<TSource>
        {
            source.EnsureNotNull(nameof(source));
            other.EnsureNotNull(nameof(other));

            var leftEnumerator = source.GetEnumerator();
            var rightEnumerator = other.GetEnumerator();
            var leftMoved = false;
            var rightMoved = false;

            while (true)
            {
                leftMoved = leftEnumerator.MoveNext();
                rightMoved = rightEnumerator.MoveNext();

                if (!leftMoved && !rightMoved) return 0;
                else if (!leftMoved && rightMoved) return -1;
                else if (leftMoved && !rightMoved) return 1;

                var comparison = leftEnumerator.Current
                    .CompareTo(rightEnumerator.Current);

                if (comparison != 0) return comparison;
            }
        }

        /// <summary>
        /// Compares the values in two <see cref="IEnumerable{TSource}"/>
        /// instances, element-by-element, using a key-selection function.
        /// </summary>
        /// <typeparam name="TSource">
        /// The type of the items in the source enumerables.
        /// </typeparam>
        /// <typeparam name="TKey">
        /// The type of the key.
        /// This type must implement <see cref="IComparable{T}" />.
        /// </typeparam>
        /// <param name="source">The source enumerable to operate on.</param>
        /// <param name="other">The enumerable to compare to.</param>
        /// <param name="selector">The key-selection function.</param>
        /// <exception cref="ArgumentNullException">
        /// The source is null.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// The other is null.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// The key-selection function is null.
        /// </exception>
        /// <returns>
        /// The first non-zero value of element-by-element comparison after
        /// applying the key-selection function, or the value -1 if the first
        /// sequence is shorter, the value +1 if the second sequence is shorter,
        /// or finally the value 0.
        /// </returns>
        public static int CompareWith<TSource, TKey>(
            this IEnumerable<TSource> source,
            IEnumerable<TSource> other,
            Func<TSource, TKey> selector)
            where TKey : IComparable<TKey>
        {
            selector.EnsureNotNull(nameof(selector));

            var source2 = source.Select(selector);
            var other2 = other.Select(selector);

            return source2.CompareWith(other2);
        }
    }
}
<file_sep>using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Chains;

namespace Chains.Test
{
    [TestClass]
    public class CompareWithTests
    {
        private const string ExpectedNegative = "Expected a negative integer.";
        private const string ExpectedPositive = "Expected a positive integer.";
        private const string ExpectedZero = "Expected zero.";

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void CompareWith_Should_ThrowIfSourceIsNull()
        {
            IEnumerable<int> first = null;
            IEnumerable<int> second = Enumerable.Range(0, 3);

            var result = first.CompareWith(second);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void CompareWith_Should_ThrowIfOtherIsNull()
        {
            IEnumerable<int> first = Enumerable.Range(0, 3);
            IEnumerable<int> second = null;

            var result = first.CompareWith(second);
        }

        [TestMethod]
        public void CompareWith_Should_ReturnNegativeIfFirstSequenceIsLesser()
        {
            var first = new List<int> { 1, 2, 3, 0 };
            var second = new List<int> { 1, 2, 3, 4 };

            Assert.IsTrue(first.CompareWith(second) < 0, ExpectedNegative);
        }

        [TestMethod]
        public void CompareWith_Should_ReturnNegativeIfFirstSequenceIsShorter()
        {
            var first = new List<int> { 1, 2, 3 };
            var second = new List<int> { 1, 2, 3, 4 };

            Assert.IsTrue(first.CompareWith(second) < 0, ExpectedNegative);
        }

        [TestMethod]
        public void CompareWith_Should_ReturnPositiveIfSecondSequenceIsLesser()
        {
            var first = new List<int> { 1, 2, 3, 4 };
            var second = new List<int> { 1, 2, 3, 0 };

            Assert.IsTrue(first.CompareWith(second) > 0, ExpectedPositive);
        }

        [TestMethod]
        public void CompareWith_Should_ReturnPositiveIfSecondSequenceIsShorter()
        {
            var first = new List<int> { 1, 2, 3, 4 };
            var second = new List<int> { 1, 2, 3 };

            Assert.IsTrue(first.CompareWith(second) > 0, ExpectedPositive);
        }

        [TestMethod]
        public void CompareWith_Should_ReturnZeroIfSequencesAreEqual()
        {
            var first = new List<int> { 1, 2, 3, 4 };
            var second = new List<int> { 1, 2, 3, 4 };

            Assert.IsTrue(first.CompareWith(second) == 0, ExpectedZero);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void CompareWithFunc_Should_ThrowIfSourceIsNull()
        {
            IEnumerable<int> first = null;
            IEnumerable<int> second = Enumerable.Range(0, 3);

            var result = first.CompareWith(second, x => (int)x);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void CompareWithFunc_Should_ThrowIfOtherIsNull()
        {
            IEnumerable<int> first = Enumerable.Range(0, 3);
            IEnumerable<int> second = null;

            var result = first.CompareWith(second, x => (int)x);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void CompareWithFunc_Should_ThrowIfSelectorIsNull()
        {
            IEnumerable<int> first = Enumerable.Range(0, 3);
            IEnumerable<int> second = Enumerable.Range(0, 3);
            Func<int, int> selector = null;

            var result = first.CompareWith(second, selector);
        }

        [TestMethod]
        public void CompareWithFunc_Should_ReturnNegativeIfFirstSequenceIsLesser()
        {
            var first = new List<string> { "1", "2", "3", "0" };
            var second = new List<string> { "1", "2", "3", "4" };

            Assert.IsTrue(first.CompareWith(second, int.Parse) < 0, ExpectedNegative);
        }

        [TestMethod]
        public void CompareWithFunc_Should_ReturnNegativeIfFirstSequenceIsShorter()
        {
            var first = new List<string> { "1", "2", "3" };
            var second = new List<string> { "1", "2", "3", "4" };

            Assert.IsTrue(first.CompareWith(second, int.Parse) < 0, ExpectedNegative);
        }

        [TestMethod]
        public void CompareWithFunc_Should_ReturnPositiveIfSecondSequenceIsLesser()
        {
            var first = new List<string> { "1", "2", "3", "4" };
            var second = new List<string> { "1", "2", "3", "0" };

            Assert.IsTrue(first.CompareWith(second, int.Parse) > 0, ExpectedPositive);
        }

        [TestMethod]
        public void CompareWithFunc_Should_ReturnPositiveIfSecondSequenceIsShorter()
        {
            var first = new List<string> { "1", "2", "3", "4" };
            var second = new List<string> { "1", "2", "3" };

            Assert.IsTrue(first.CompareWith(second, int.Parse) > 0, ExpectedPositive);
        }

        [TestMethod]
        public void CompareWithFunc_Should_ReturnZeroIfSequencesAreEqual()
        {
            var first = new List<string> { "1", "2", "3", "4" };
            var second = new List<string> { "1", "2", "3", "4" };

            Assert.IsTrue(first.CompareWith(second, int.Parse) == 0, ExpectedZero);
        }
    }
}
<file_sep>using System;
using System.Collections.Generic;
using System.Linq;

namespace Chains
{
    public static partial class Chains
    {
        /// <summary>
        /// Produces a sequence of fixed-size windows over the input sequence.
        /// Windows are produced until there are not enough remaining elements
        /// to fill an entire window.
        /// </summary>
        /// <typeparam name="TSource">
        /// The type of the items in the source enumerable.
        /// </typeparam>
        /// <param name="source">The source enumerable to operate on.</param>
        /// <param name="size">The number of elements in each window.</param>
        /// <exception cref="ArgumentNullException">
        /// The source is null.
        /// </exception>
        /// <exception cref="ArgumentOutOfRangeException">
        /// The size is less than one.
        /// </exception>
        /// <returns>
        /// A sequence of windows over the original sequence.
        /// </returns>
        public static IEnumerable<IEnumerable<TSource>> Windowed<TSource>(
            this IEnumerable<TSource> source,
            int size)
        {
            source.EnsureNotNull(nameof(source));
            size.EnsureGreaterThan(0, nameof(size));

            if (!source.Any()) yield break;

            var seq = source;
            var iter = seq.Skip(size - 1).GetEnumerator();
            while (iter.MoveNext())
            {
                yield return seq.Take(size);
                seq = seq.Skip(1);
            }
            yield break;
        }
    }
}
<file_sep>using System;
using System.Collections.Generic;
using System.Linq;

namespace Chains
{
    public static partial class Chains
    {
        /// <summary>
        /// Applies an accumulator function over a sequence of values, yielding
        /// a state value each iteration. The seed is used as the initial
        /// accumulator value, and each intermediate state is transformed using
        /// a transformation function.
        /// </summary>
        /// <typeparam name="TSource">
        /// The type of the items in the source enumerable.
        /// </typeparam>
        /// <typeparam name="TState">
        /// The type of the intermediate state.
        /// </typeparam>
        /// <typeparam name="TResult">
        /// The type of the transformed state.
        /// </typeparam>
        /// <param name="source">The source enumerable to operate on.</param>
        /// <param name="seed">The initial accumulator value.</param>
        /// <param name="accumulator">The accumulator function.</param>
        /// <param name="transformer">The transformation function.</param>
        /// <exception cref="ArgumentNullException">
        /// The source is null.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// The accumulator function is null.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// The transformation function is null.
        /// </exception>
        /// <returns>
        /// The sequence of transformed intermediate states of the accumulator.
        /// </returns>
        public static IEnumerable<TResult> Scan<TSource, TState, TResult>(
            this IEnumerable<TSource> source,
            TState seed,
            Func<TState, TSource, TState> accumulator,
            Func<TState, TResult> transformer)
        {
            return source
                .Scan(seed, accumulator)
                .Select(transformer);
        }

        /// <summary>
        /// Applies an accumulator function over a sequence of values, yielding
        /// a state value each iteration. The seed is used as the initial
        /// accumulator value.
        /// </summary>
        /// <typeparam name="TSource">
        /// The type of the items in the source enumerable.
        /// </typeparam>
        /// <typeparam name="TState">
        /// The type of the intermediate state.
        /// </typeparam>
        /// <param name="source">The source enumerable to operate on.</param>
        /// <param name="seed">The initial accumulator value.</param>
        /// <param name="accumulator">The accumulator function.</param>
        /// <exception cref="ArgumentNullException">
        /// The source is null.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// The accumulator function is null.
        /// </exception>
        /// <returns>
        /// The sequence of intermediate states of the accumulator.
        /// </returns>
        public static IEnumerable<TState> Scan<TSource, TState>(
            this IEnumerable<TSource> source,
            TState seed,
            Func<TState, TSource, TState> accumulator)
        {
            source.EnsureNotNull(nameof(source));
            accumulator.EnsureNotNull(nameof(accumulator));

            yield return seed;

            var last = seed;
            foreach (var item in source)
            {
                last = accumulator(last, item);
                yield return last;
            }
        }

        /// <summary>
        /// Applies an accumulator function over a sequence of values, yielding
        /// a state value each iteration. The initial accumulator value is the
        /// first value in the sequence.
        /// </summary>
        /// <typeparam name="TSource">
        /// The type of the items in the source enumerable.
        /// </typeparam>
        /// <param name="source">The source enumerable to operate on.</param>
        /// <param name="accumulator">The accumulator function.</param>
        /// <exception cref="ArgumentNullException">
        /// The source is null.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// The accumulator function is null.
        /// </exception>
        /// <returns>
        /// The sequence of intermediate states of the accumulator.
        /// </returns>
        public static IEnumerable<TSource> Scan<TSource>(
            this IEnumerable<TSource> source,
            Func<TSource, TSource, TSource> accumulator)
        {
            source.EnsureNotNull(nameof(source));

            if (!source.Any()) return Enumerable.Empty<TSource>();

            var first = source.First();
            var rest = source.Skip(1);

            return rest.Scan(first, accumulator);
        }
    }
}
<file_sep>using System;
using System.Collections.Generic;

namespace Chains
{
    public static partial class Chains
    {
        /// <summary>
        /// Yields the distinct values in an <see cref="IEnumerable{TSource}"/>
        /// according to a key-selection function.
        /// </summary>
        /// <typeparam name="TSource">
        /// The type of the items in the source enumerable.
        /// </typeparam>
        /// <typeparam name="TKey">
        /// The type of the key.
        /// This type must implement <see cref="IEquatable{T}" />.
        /// </typeparam>
        /// <param name="source">The source enumerable to operate on.</param>
        /// <param name="selector">The key-selection function to use.</param>
        /// <exception cref="ArgumentNullException">
        /// The source is null.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// The selector is null.
        /// </exception>
        /// <returns>
        /// A sequence of values distinct by the key-selection function.
        /// </returns>
        public static IEnumerable<TSource> DistinctBy<TSource, TKey>(
            this IEnumerable<TSource> source,
            Func<TSource, TKey> selector)
            where TKey : IEquatable<TKey>
        {
            source.EnsureNotNull(nameof(source));
            selector.EnsureNotNull(nameof(selector));

            var hashSet = new HashSet<TKey>();

            foreach (var item in source)
                if (hashSet.Add(selector(item))) yield return item;
        }
    }
}
<file_sep># Chains.NET

Spice up your enumerables with extra method chains.

## About

This library provides some extra niceties to the `IEnumerable<T>` interface. Many .NET developers enjoy using the standard library features in `System.Linq`. Chains.NET aims to make that experience even better by providing more operations on sequences, inspired by a myriad of functional languages.

*This project is a work-in-progress. One day I hope to be able to offer it on NuGet.*

## FAQ

**Q:** Why "Chains.NET"?

**A:** This libary provides chainable (a.k.a fluent) methods, on top of what LINQ offers, and lots of open source projects in the .NET ecosystem use ".NET" in their name. Finally, as a software engineer, by default I am not good at naming things.
<file_sep>using System;
using System.Collections.Generic;

namespace Chains
{
    public static partial class Chains
    {
        /// <summary>
        /// Performs a given action on each element in an
        /// <see cref="IEnumerable{T}" />.
        /// </summary>
        /// <typeparam name="TSource">
        /// The type of the items in the source enumerable.
        /// </typeparam>
        /// <param name="source">The source enumerable to operate on.</param>
        /// <param name="action">The action to perform on each element.</param>
        /// <exception cref="ArgumentNullException">
        /// The source is null.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// The action is null.
        /// </exception>
        public static void ForEach<TSource>(
            this IEnumerable<TSource> source,
            Action<TSource> action)
        {
            source.EnsureNotNull(nameof(source));
            action.EnsureNotNull(nameof(action));

            foreach (var element in source) action(element);
        }
    }
}
<file_sep>using System;

namespace Chains
{
    internal static class RequirementHelper
    {
        private const string ArgumentTooSmallMessage = "Given argument was less than the specified minimum of {0}.";
        private const string ArgumentTooLargeMessage = "Given argument was greater than the specified maximum of {0}.";

        internal static TSource EnsureNotNull<TSource>(this TSource source, string name)
        {
            if (source == null)
                throw new ArgumentNullException(name);
            return source;
        }

        internal static TSource EnsureBetweenInclusive<TSource, TCompare>(this TSource source, TCompare min, TCompare max, string name)
            where TSource : IComparable<TCompare>
        {
            if (source.CompareTo(min) < 0)
                throw new ArgumentOutOfRangeException(name, source, string.Format(ArgumentTooSmallMessage, min));
            else if (source.CompareTo(max) > 0)
                throw new ArgumentOutOfRangeException(name, source, string.Format(ArgumentTooLargeMessage, max));
            return source;
        }

        internal static TSource EnsureGreaterThan<TSource, TCompare>(this TSource source, TCompare other, string name)
            where TSource : IComparable<TCompare>
        {
            if (source.CompareTo(other) <= 0)
                throw new ArgumentOutOfRangeException(name, source, string.Format(ArgumentTooSmallMessage, other));
            return source;
        }

        internal static TSource EnsureLessThan<TSource, TCompare>(this TSource source, TCompare other, string name)
            where TSource : IComparable<TCompare>
        {
            if (source.CompareTo(other) >= 0)
                throw new ArgumentOutOfRangeException(name, source, string.Format(ArgumentTooLargeMessage, other));
            return source;
        }

        internal static TSource Ensure<TSource>(this TSource source, Func<TSource, bool> requirement, string name, string message)
        {
            if (!requirement(source))
                throw new ArgumentException(name, message);
            return source;
        }
    }
}
<file_sep>using System;
using System.Linq;
using System.Collections.Generic;

namespace Chains
{
    public static partial class Chains
    {
        /// <summary>
        /// Yields the values in an <see cref="IEnumerable{TSource}"/>
        /// except the first element.
        /// </summary>
        /// <typeparam name="TSource">
        /// The type of the items in the source enumerable.
        /// </typeparam>
        /// <param name="source">The source enumerable to operate on.</param>
        /// <exception cref="ArgumentNullException">
        /// The source is null.
        /// </exception>
        /// <exception cref="InvalidOperationException">
        /// The source is empty.
        /// </exception>
        /// <returns>
        /// An enumerable containing all except the first element in the source.
        /// </returns>
        public static IEnumerable<TSource> ExceptFirst<TSource>(
            this IEnumerable<TSource> source)
        {
            source.EnsureNotNull(nameof(source));
            // TODO: Replace with helper method
            if (!source.Any()) throw new InvalidOperationException();

            foreach (var item in source.Skip(1)) yield return item;
        }
    }
}
<file_sep>using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Chains;

namespace Chains.Test
{
    [TestClass]
    public class ExceptFirstTests
    {
        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ExceptFirst_Should_ThrowIfSourceIsNull()
        {
            var sequence = (IEnumerable<object>)null;

            var actual = sequence.ExceptFirst().ToList();
        }

        [TestMethod]
        [ExpectedException(typeof(InvalidOperationException))]
        public void ExceptFirst_Should_ThrowIfSourceIsEmpty()
        {
            var sequence = Enumerable.Empty<object>();

            var actual = sequence.ExceptFirst().ToList();
        }

        [TestMethod]
        public void ExceptFirst_Should_YieldAllElementsExceptTheFirst()
        {
            var sequence = Enumerable.Range(1, 3);
            var expected = new[]{ 2, 3 };

            var actual = sequence.ExceptFirst();

            Assert.IsTrue(
                actual.SequenceEqual(expected),
                "Actual and expected sequences did not match.");
        }

        [TestMethod]
        public void ExceptFirst_Should_BeTheInverseOfFirst()
        {
            var sequence = Enumerable.Range(1, 3);

            var first = sequence.First();
            var exceptFirst = sequence.ExceptFirst();
            var concat = exceptFirst.Prepend(first);

            Assert.IsTrue(
                concat.SequenceEqual(sequence),
                "Inverse constraint failed.");
        }

    }
}
<file_sep>using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Chains;

namespace Chains.Test
{
    [TestClass]
    public class ForEachTests
    {
        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ForEach_Should_ThrowIfSourceIsNull()
        {
            IEnumerable<int> seq = null;

            seq.ForEach(Console.WriteLine);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ForEach_Should_ThrowIfActionIsNull()
        {
            var seq = Enumerable.Range(0, 10);
            Action<int> action = null;

            seq.ForEach(action);
        }

        [TestMethod]
        public void ForEach_Should_ExecuteTheActionForEachItem()
        {
            var count = 0;
            var expected = 6;
            var seq = Enumerable.Range(0, 4);
            void Increment(int i) => count += i;

            seq.ForEach(Increment);

            Assert.AreEqual(expected, count, "Did not produce expected value.");
        }
    }
}
<file_sep>using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Chains;

namespace Chains.Test
{
    [TestClass]
    public class RequirementHelperTests
    {
        private const string DidNotReturnSameReference = "Did not return the same reference.";
        private const string DidNotReturnEqualValue = "Did not return equal value.";

        [TestMethod]
        public void EnsureNotNull_Should_ReturnSameObjectIfNotNull()
        {
            var expected = "Hello, world!";

            var actual = expected.EnsureNotNull(nameof(expected));

            Assert.AreSame(expected, actual, DidNotReturnSameReference);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void EnsureNotNull_Should_ThrowIfNull()
        {
            var expected = (string)null;

            var actual = expected.EnsureNotNull(nameof(expected));
        }

        [TestMethod]
        public void EnsureBetweenInclusive_Should_ReturnSameObjectIfBetweenInclusive()
        {
            var expected = 5;
            var min = 0;
            var max = 10;

            var actual = expected.EnsureBetweenInclusive(min, max, nameof(expected));

            Assert.AreEqual(expected, actual, DidNotReturnEqualValue);
        }

        [TestMethod]
        public void EnsureBetweenInclusive_Should_NotThrowIfEqualToMinimum()
        {
            var expected = 0;
            var min = 0;
            var max = 10;

            var actual = expected.EnsureBetweenInclusive(min, max, nameof(expected));

            Assert.AreEqual(expected, actual, DidNotReturnEqualValue);
        }

        [TestMethod]
        public void EnsureBetweenInclusive_Should_NotThrowIfEqualToMaximum()
        {
            var expected = 10;
            var min = 0;
            var max = 10;

            var actual = expected.EnsureBetweenInclusive(min, max, nameof(expected));

            Assert.AreEqual(expected, actual, DidNotReturnEqualValue);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void EnsureBetweenInclusive_Should_ThrowIfLessThanMinimum()
        {
            var expected = -1;
            var min = 0;
            var max = 10;

            var actual = expected.EnsureBetweenInclusive(min, max, nameof(expected));
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void EnsureBetweenInclusive_Should_ThrowIfGreaterThanMaximum()
        {
            var expected = 11;
            var min = 0;
            var max = 10;

            var actual = expected.EnsureBetweenInclusive(min, max, nameof(expected));
        }

        [TestMethod]
        public void EnsureGreaterThan_Should_ReturnSameObjectIfGreaterThanOther()
        {
            var expected = "abc";
            var other = "aaa";

            var actual = expected.EnsureGreaterThan(other, nameof(expected));

            Assert.AreSame(expected, actual, DidNotReturnSameReference);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void EnsureGreaterThan_Should_ThrowIfEqualToOther()
        {
            var expected = "abc";
            var other = "abc";

            var actual = expected.EnsureGreaterThan(other, nameof(expected));
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void EnsureGreaterThan_Should_ThrowIfLesserThanOther()
        {
            var expected = "abc";
            var other = "zzz";

            var actual = expected.EnsureGreaterThan(other, nameof(expected));
        }

        [TestMethod]
        public void EnsureLessThan_Should_ReturnSameObjectIfLessserThanOther()
        {
            var expected = "abc";
            var other = "zzz";

            var actual = expected.EnsureLessThan(other, nameof(expected));

            Assert.AreSame(expected, actual, DidNotReturnSameReference);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void EnsureLessThan_Should_ThrowIfEqualToOther()
        {
            var expected = "abc";
            var other = "abc";

            var actual = expected.EnsureLessThan(other, nameof(expected));
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void EnsureLessThan_Should_ThrowIfGreaterThanOther()
        {
            var expected = "abc";
            var other = "aaa";

            var actual = expected.EnsureLessThan(other, nameof(expected));
        }

        [TestMethod]
        public void Ensure_Should_ReturnSameInstanceIfRequirmentPassed()
        {
            var expected = "Hello, world.";

            var actual = expected.Ensure(s => true, nameof(expected), "Did not pass expectation.");

            Assert.AreSame(expected, actual, DidNotReturnEqualValue);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void Ensure_Should_ThrowIfRequirementFailed()
        {
            var expected = "Hello, world!";

            var actual = expected.Ensure(s => false, nameof(expected), "Passed expectation.");
        }
    }
}
<file_sep>using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Chains;

namespace Chains.Test
{
    [TestClass]
    public class WindowedTests
    {
        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Windowed_Should_ThrowIfSourceIsNull()
        {
            var sequence = (IEnumerable<object>)null;

            var actual = sequence.Windowed(1).ToList();
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void Windowed_Should_ThrowIfSizeIsZero()
        {
            var sequence = Enumerable.Range(0, 10);
            var size = 0;

            var actual = sequence.Windowed(size).ToList();
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void Windowed_Should_ThrowIfSizeIsNegative()
        {
            var sequence = Enumerable.Range(0, 10);
            var size = -1;

            var actual = sequence.Windowed(size).ToList();
        }

        [TestMethod]
        public void Windowed_Should_ProduceAnEmptySequenceIfNotEnoughElements()
        {
            var sequence = Enumerable.Range(1, 1);
            var size = 2;

            var actual = sequence.Windowed(size);

            Assert.IsFalse(actual.Any(), "Expected empty sequence.");
        }

        [TestMethod]
        public void Windowed_Should_ProduceFixedSizeWindowsUntilEnd()
        {
            var sequence = new[]{ 1, 2, 3, 4, 5 };
            var size = 3;
            var expected = new List<int[]> {
                new[]{ 1, 2, 3 },
                new[]{ 2, 3, 4 },
                new[]{ 3, 4, 5 }
            };

            var actual = sequence.Windowed(size);

            Assert.AreEqual(expected.Count, actual.Count(), "Mismatched counts at top level.");
            foreach (var window in actual)
                Assert.AreEqual(size, window.Count(), "Mismatched counts at window level.");
            Assert.IsTrue(
                expected
                    .SelectMany(x => x)
                    .SequenceEqual(actual.SelectMany(x => x)),
                "Expected sequences to match.");
        }
    }
}
<file_sep>using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Chains;

namespace Chains.Tests
{
    [TestClass]
    public class ProductTests
    {
        [TestMethod]
        public void ProductOfInts_Should_ComputeTheProduct()
        {
            var ints = new[]{ 1, 2, 3 };
            var expected = 1 * 2 * 3;

            var actual = ints.Product();

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void ProductOfInts_Should_ReturnOneForEmptySequence()
        {
            var ints = Enumerable.Empty<int>();
            var expected = 1;

            var actual = ints.Product();

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ProductOfInts_Should_ThrowForNullSequence()
        {
            var ints = (IEnumerable<int>)null;

            var actual = ints.Product();
        }

        [TestMethod]
        [ExpectedException(typeof(OverflowException))]
        public void ProductOfInts_Should_ThrowOnOverflow()
        {
            var ints = new[]{ int.MaxValue, 10 };

            var actual = ints.Product();
        }

        [TestMethod]
        public void ProductOfNullableInts_Should_ComputeTheProduct()
        {
            var ints = new int?[]{ 1, 2, 3 };
            var expected = (int?)(1 * 2 * 3);

            var actual = ints.Product();

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void ProductOfNullableInts_Should_IgnoreNullValues()
        {
            var ints = new int?[]{ 1, null, 2, null, 3, null };
            var expected = (int?)(1 * 2 * 3);

            var actual = ints.Product();

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void ProductOfNullableInts_Should_ReturnOneForEmptySequence()
        {
            var ints = Enumerable.Empty<int?>();
            var expected = (int?)1;

            var actual = ints.Product();

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ProductOfNullableInts_Should_ThrowForNullSequence()
        {
            var ints = (IEnumerable<int?>)null;

            var actual = ints.Product();
        }

        [TestMethod]
        [ExpectedException(typeof(OverflowException))]
        public void ProductOfNullableInts_Should_ThrowOnOverflow()
        {
            var ints = new int?[]{ int.MaxValue, 10 };

            var actual = ints.Product();
        }

        [TestMethod]
        public void ProductOfLongs_Should_ComputeTheProduct()
        {
            var longs = new[]{ 1L, 2L, 3L };
            var expected = 1L * 2L * 3L;

            var actual = longs.Product();

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void ProductOfLongs_Should_ReturnOneForEmptySequence()
        {
            var longs = Enumerable.Empty<long>();
            var expected = 1L;

            var actual = longs.Product();

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ProductOfLongs_Should_ThrowForNullSequence()
        {
            var longs = (IEnumerable<long>)null;

            var actual = longs.Product();
        }

        [TestMethod]
        [ExpectedException(typeof(OverflowException))]
        public void ProductOfLongs_Should_ThrowOnOverflow()
        {
            var longs = new[]{ long.MaxValue, 10L };

            var actual = longs.Product();
        }

        [TestMethod]
        public void ProductOfNullableLongs_Should_ComputeTheProduct()
        {
            var longs = new long?[]{ 1L, 2L, 3L };
            var expected = (long?)(1L * 2L * 3L);

            var actual = longs.Product();

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void ProductOfNullableLongs_Should_IgnoreNullValues()
        {
            var longs = new long?[]{ 1L, null, 2L, null, 3L, null };
            var expected = (long?)(1L * 2L * 3L);

            var actual = longs.Product();

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void ProductOfNullableLongs_Should_ReturnOneForEmptySequence()
        {
            var longs = Enumerable.Empty<long?>();
            var expected = (long?)1L;

            var actual = longs.Product();

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ProductOfNullableLongs_Should_ThrowForNullSequence()
        {
            var longs = (IEnumerable<long?>)null;

            var actual = longs.Product();
        }

        [TestMethod]
        [ExpectedException(typeof(OverflowException))]
        public void ProductOfNullableLongs_Should_ThrowOnOverflow()
        {
            var longs = new long?[]{ long.MaxValue, 10L };

            var actual = longs.Product();
        }

        [TestMethod]
        public void ProductOfFloats_Should_ComputeTheProduct()
        {
            var floats = new[]{ 1.0F, 2.0F, 3.0F };
            var expected = 1.0F * 2.0F * 3.0F;

            var actual = floats.Product();

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void ProductOfFloats_Should_ReturnOneForEmptySequence()
        {
            var floats = Enumerable.Empty<float>();
            var expected = 1.0F;

            var actual = floats.Product();

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ProductOfFloats_Should_ThrowForNullSequence()
        {
            var floats = (IEnumerable<float>)null;

            var actual = floats.Product();
        }

        [TestMethod]
        public void ProductOfNullableFloats_Should_ComputeTheProduct()
        {
            var floats = new float?[]{ 1.0F, 2.0F, 3.0F };
            var expected = (float?)(1.0F * 2.0F * 3.0F);

            var actual = floats.Product();

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void ProductOfNullableFloats_Should_IgnoreNullValues()
        {
            var floats = new float?[]{ 1.0F, null, 2.0F, null, 3.0F, null };
            var expected = (float?)(1.0F * 2.0F * 3.0F);

            var actual = floats.Product();

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void ProductOfNullableFloats_Should_ReturnOneForEmptySequence()
        {
            var floats = Enumerable.Empty<float?>();
            var expected = (float?)1.0F;

            var actual = floats.Product();

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ProductOfNullableFloats_Should_ThrowForNullSequence()
        {
            var floats = (IEnumerable<float?>)null;

            var actual = floats.Product();
        }

        [TestMethod]
        public void ProductOfDoubles_Should_ComputeTheProduct()
        {
            var doubles = new[]{ 1.0, 2.0, 3.0 };
            var expected = 1.0 * 2.0 * 3.0;

            var actual = doubles.Product();

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void ProductOfDoubles_Should_ReturnOneForEmptySequence()
        {
            var doubles = Enumerable.Empty<double>();
            var expected = 1.0;

            var actual = doubles.Product();

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ProductOfDoubles_Should_ThrowForNullSequence()
        {
            var doubles = (IEnumerable<double>)null;

            var actual = doubles.Product();
        }

        [TestMethod]
        public void ProductOfNullableDoubles_Should_ComputeTheProduct()
        {
            var doubles = new double?[]{ 1.0, 2.0, 3.0 };
            var expected = (double?)(1.0 * 2.0 * 3.0);

            var actual = doubles.Product();

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void ProductOfNullableDoubles_Should_IgnoreNullValues()
        {
            var doubles = new double?[]{ 1.0, null, 2.0, null, 3.0, null };
            var expected = (double?)(1.0 * 2.0 * 3.0);

            var actual = doubles.Product();

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void ProductOfNullableDoubles_Should_ReturnOneForEmptySequence()
        {
            var doubles = Enumerable.Empty<double?>();
            var expected = (double?)1.0;

            var actual = doubles.Product();

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ProductOfNullableDoubles_Should_ThrowForNullSequence()
        {
            var doubles = (IEnumerable<double?>)null;

            var actual = doubles.Product();
        }

        [TestMethod]
        public void ProductOfDecimals_Should_ComputeTheProduct()
        {
            var decimals = new[]{ 1.0M, 2.0M, 3.0M };
            var expected = 1.0M * 2.0M * 3.0M;

            var actual = decimals.Product();

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void ProductOfDecimals_Should_ReturnOneForEmptySequence()
        {
            var decimals = Enumerable.Empty<decimal>();
            var expected = 1.0M;

            var actual = decimals.Product();

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ProductOfDecimals_Should_ThrowForNullSequence()
        {
            var decimals = (IEnumerable<decimal>)null;

            var actual = decimals.Product();
        }

        [TestMethod]
        [ExpectedException(typeof(OverflowException))]
        public void ProductOfDecimals_Should_ThrowOnOverflow()
        {
            var decimals = new[]{ decimal.MaxValue, 10.0M };

            var actual = decimals.Product();
        }

        [TestMethod]
        public void ProductOfNullableDecimals_Should_ComputeTheProduct()
        {
            var decimals = new decimal?[]{ 1.0M, 2.0M, 3.0M };
            var expected = (decimal?)(1.0M * 2.0M * 3.0M);

            var actual = decimals.Product();

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void ProductOfNullableDecimals_Should_IgnoreNullValues()
        {
            var decimals = new decimal?[]{ 1.0M, null, 2.0M, null, 3.0M, null };
            var expected = (decimal?)(1.0M * 2.0M * 3.0M);

            var actual = decimals.Product();

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void ProductOfNullableDecimals_Should_ReturnOneForEmptySequence()
        {
            var decimals = Enumerable.Empty<decimal?>();
            var expected = (decimal?)1.0M;

            var actual = decimals.Product();

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ProductOfNullableDecimals_Should_ThrowForNullSequence()
        {
            var decimals = (IEnumerable<decimal?>)null;

            var actual = decimals.Product();
        }

        [TestMethod]
        [ExpectedException(typeof(OverflowException))]
        public void ProductOfNullableDecimals_Should_ThrowOnOverflow()
        {
            var decimals = new decimal?[]{ decimal.MaxValue, 10.0M };

            var actual = decimals.Product();
        }

        [TestMethod]
        public void ProductOfTransformedInts_Should_ComputeTheProduct()
        {
            var ints = new[]{ 1, 2, 3 };
            var expected = 1 * 2 * 3;

            var actual = ints.Product(x => x);

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void ProductOfTransformedInts_Should_ReturnOneForEmptySequence()
        {
            var ints = Enumerable.Empty<int>();
            var expected = 1;

            var actual = ints.Product(x => x);

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ProductOfTransformedInts_Should_ThrowForNullSequence()
        {
            var ints = (IEnumerable<int>)null;

            var actual = ints.Product(x => x);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ProductOfTransformedInts_Should_ThrowForNullSelector()
        {
            var ints = new[]{ 1, 2, 3 };

            var actual = ints.Product((Func<int, int>)null);
        }

        [TestMethod]
        [ExpectedException(typeof(OverflowException))]
        public void ProductOfTransformedInts_Should_ThrowOnOverflow()
        {
            var ints = new[]{ int.MaxValue, 10 };

            var actual = ints.Product(x => x);
        }

        [TestMethod]
        public void ProductOfTransformedNullableInts_Should_ComputeTheProduct()
        {
            var ints = new int?[]{ 1, 2, 3 };
            var expected = (int?)(1 * 2 * 3);

            var actual = ints.Product(x => x);

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void ProductOfTransformedNullableInts_Should_IgnoreNullValues()
        {
            var ints = new int?[]{ 1, null, 2, null, 3, null };
            var expected = (int?)(1 * 2 * 3);

            var actual = ints.Product(x => x);

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void ProductOfTransformedNullableInts_Should_ReturnOneForEmptySequence()
        {
            var ints = Enumerable.Empty<int?>();
            var expected = (int?)1;

            var actual = ints.Product(x => x);

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ProductOfTransformedNullableInts_Should_ThrowForNullSequence()
        {
            var ints = (IEnumerable<int?>)null;

            var actual = ints.Product(x => x);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ProductOfTransformedNullableInts_Should_ThrowForNullSelector()
        {
            var ints = new int?[]{ 1, 2, 3 };

            var actual = ints.Product((Func<int?, int?>)null);
        }

        [TestMethod]
        [ExpectedException(typeof(OverflowException))]
        public void ProductOfTransformedNullableInts_Should_ThrowOnOverflow()
        {
            var ints = new int?[]{ int.MaxValue, 10 };

            var actual = ints.Product(x => x);
        }

        [TestMethod]
        public void ProductOfTransformedLongs_Should_ComputeTheProduct()
        {
            var longs = new[]{ 1L, 2L, 3L };
            var expected = 1L * 2L * 3L;

            var actual = longs.Product(x => x);

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void ProductOfTransformedLongs_Should_ReturnOneForEmptySequence()
        {
            var longs = Enumerable.Empty<long>();
            var expected = 1L;

            var actual = longs.Product(x => x);

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ProductOfTransformedLongs_Should_ThrowForNullSequence()
        {
            var longs = (IEnumerable<long>)null;

            var actual = longs.Product(x => x);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ProductOfTransformedLongs_Should_ThrowForNullSelector()
        {
            var longs = new[]{ 1L, 2L, 3L };

            var actual = longs.Product((Func<long, long>)null);
        }

        [TestMethod]
        [ExpectedException(typeof(OverflowException))]
        public void ProductOfTransformedLongs_Should_ThrowOnOverflow()
        {
            var longs = new[]{ long.MaxValue, 10L };

            var actual = longs.Product(x => x);
        }

        [TestMethod]
        public void ProductOfTransformedNullableLongs_Should_ComputeTheProduct()
        {
            var longs = new long?[]{ 1L, 2L, 3L };
            var expected = (long?)(1L * 2L * 3L);

            var actual = longs.Product(x => x);

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void ProductOfTransformedNullableLongs_Should_IgnoreNullValues()
        {
            var longs = new long?[]{ 1L, null, 2L, null, 3L, null };
            var expected = (long?)(1L * 2L * 3L);

            var actual = longs.Product(x => x);

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void ProductOfTransformedNullableLongs_Should_ReturnOneForEmptySequence()
        {
            var longs = Enumerable.Empty<long?>();
            var expected = (long?)1L;

            var actual = longs.Product(x => x);

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ProductOfTransformedNullableLongs_Should_ThrowForNullSequence()
        {
            var longs = (IEnumerable<long?>)null;

            var actual = longs.Product(x => x);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ProductOfTransformedNullableLongs_Should_ThrowForNullSelector()
        {
            var longs = new long?[]{ 1L, 2L, 3L };

            var actual = longs.Product((Func<long?, long?>)null);
        }

        [TestMethod]
        [ExpectedException(typeof(OverflowException))]
        public void ProductOfTransformedNullableLongs_Should_ThrowOnOverflow()
        {
            var longs = new long?[]{ long.MaxValue, 10L };

            var actual = longs.Product(x => x);
        }

        [TestMethod]
        public void ProductOfTransformedFloats_Should_ComputeTheProduct()
        {
            var floats = new[]{ 1.0F, 2.0F, 3.0F };
            var expected = 1.0F * 2.0F * 3.0F;

            var actual = floats.Product(x => x);

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void ProductOfTransformedFloats_Should_ReturnOneForEmptySequence()
        {
            var floats = Enumerable.Empty<float>();
            var expected = 1.0F;

            var actual = floats.Product(x => x);

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ProductOfTransformedFloats_Should_ThrowForNullSequence()
        {
            var floats = (IEnumerable<float>)null;

            var actual = floats.Product(x => x);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ProductOfTransformedFloats_Should_ThrowForNullSelector()
        {
            var floats = new[]{ 1.0F, 2.0F, 3.0F };

            var actual = floats.Product((Func<float, float>)null);
        }

        [TestMethod]
        public void ProductOfTransformedNullableFloats_Should_ComputeTheProduct()
        {
            var floats = new float?[]{ 1.0F, 2.0F, 3.0F };
            var expected = (float?)(1.0F * 2.0F * 3.0F);

            var actual = floats.Product(x => x);

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void ProductOfTransformedNullableFloats_Should_IgnoreNullValues()
        {
            var floats = new float?[]{ 1.0F, null, 2.0F, null, 3.0F, null };
            var expected = (float?)(1.0F * 2.0F * 3.0F);

            var actual = floats.Product(x => x);

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void ProductOfTransformedNullableFloats_Should_ReturnOneForEmptySequence()
        {
            var floats = Enumerable.Empty<float?>();
            var expected = (float?)1.0F;

            var actual = floats.Product(x => x);

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ProductOfTransformedNullableFloats_Should_ThrowForNullSequence()
        {
            var floats = (IEnumerable<float?>)null;

            var actual = floats.Product(x => x);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ProductOfTransformedNullableFloats_Should_ThrowForNullSelector()
        {
            var floats = new float?[]{ 1.0F, 2.0F, 3.0F };

            var actual = floats.Product((Func<float?, float?>)null);
        }

        [TestMethod]
        public void ProductOfTransformedDoubles_Should_ComputeTheProduct()
        {
            var doubles = new[]{ 1.0, 2.0, 3.0 };
            var expected = 1.0 * 2.0 * 3.0;

            var actual = doubles.Product(x => x);

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void ProductOfTransformedDoubles_Should_ReturnOneForEmptySequence()
        {
            var doubles = Enumerable.Empty<double>();
            var expected = 1.0;

            var actual = doubles.Product(x => x);

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ProductOfTransformedDoubles_Should_ThrowForNullSequence()
        {
            var doubles = (IEnumerable<double>)null;

            var actual = doubles.Product(x => x);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ProductOfTransformedDoubles_Should_ThrowForNullSelector()
        {
            var doubles = new[]{ 1.0, 2.0, 3.0 };

            var actual = doubles.Product((Func<double, double>)null);
        }

        [TestMethod]
        public void ProductOfTransformedNullableDoubles_Should_ComputeTheProduct()
        {
            var doubles = new double?[]{ 1.0, 2.0, 3.0 };
            var expected = (double?)(1.0 * 2.0 * 3.0);

            var actual = doubles.Product(x => x);

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void ProductOfTransformedNullableDoubles_Should_IgnoreNullValues()
        {
            var doubles = new double?[]{ 1.0, null, 2.0, null, 3.0, null };
            var expected = (double?)(1.0 * 2.0 * 3.0);

            var actual = doubles.Product(x => x);

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void ProductOfTransformedNullableDoubles_Should_ReturnOneForEmptySequence()
        {
            var doubles = Enumerable.Empty<double?>();
            var expected = (double?)1.0;

            var actual = doubles.Product(x => x);

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ProductOfTransformedNullableDoubles_Should_ThrowForNullSequence()
        {
            var doubles = (IEnumerable<double?>)null;

            var actual = doubles.Product(x => x);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ProductOfTransformedNullableDoubles_Should_ThrowForNullSelector()
        {
            var doubles = new double?[]{ 1.0, 2.0, 3.0 };

            var actual = doubles.Product((Func<double?, double?>)null);
        }

        [TestMethod]
        public void ProductOfTransformedDecimals_Should_ComputeTheProduct()
        {
            var decimals = new[]{ 1.0M, 2.0M, 3.0M };
            var expected = 1.0M * 2.0M * 3.0M;

            var actual = decimals.Product(x => x);

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void ProductOfTransformedDecimals_Should_ReturnOneForEmptySequence()
        {
            var decimals = Enumerable.Empty<decimal>();
            var expected = 1.0M;

            var actual = decimals.Product(x => x);

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ProductOfTransformedDecimals_Should_ThrowForNullSequence()
        {
            var decimals = (IEnumerable<decimal>)null;

            var actual = decimals.Product(x => x);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ProductOfTransformedDecimals_Should_ThrowForNullSelector()
        {
            var decimals = new[]{ 1.0M, 2.0M, 3.0M };

            var actual = decimals.Product((Func<decimal, decimal>)null);
        }

        [TestMethod]
        [ExpectedException(typeof(OverflowException))]
        public void ProductOfTransformedDecimals_Should_ThrowOnOverflow()
        {
            var decimals = new[]{ decimal.MaxValue, 10.0M };

            var actual = decimals.Product(x => x);
        }

        [TestMethod]
        public void ProductOfTransformedNullableDecimals_Should_ComputeTheProduct()
        {
            var decimals = new decimal?[]{ 1.0M, 2.0M, 3.0M };
            var expected = (decimal?)(1.0M * 2.0M * 3.0M);

            var actual = decimals.Product(x => x);

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void ProductOfTransformedNullableDecimals_Should_IgnoreNullValues()
        {
            var decimals = new decimal?[]{ 1.0M, null, 2.0M, null, 3.0M, null };
            var expected = (decimal?)(1.0M * 2.0M * 3.0M);

            var actual = decimals.Product(x => x);

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void ProductOfTransformedNullableDecimals_Should_ReturnOneForEmptySequence()
        {
            var decimals = Enumerable.Empty<decimal?>();
            var expected = (decimal?)1.0M;

            var actual = decimals.Product(x => x);

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ProductOfTransformedNullableDecimals_Should_ThrowForNullSequence()
        {
            var decimals = (IEnumerable<decimal?>)null;

            var actual = decimals.Product(x => x);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ProductOfTransformedNullableDecimals_Should_ThrowForNullSelector()
        {
            var decimals = new decimal?[]{ 1.0M, 2.0M, 3.0M };

            var actual = decimals.Product((Func<decimal?, decimal?>)null);
        }

        [TestMethod]
        [ExpectedException(typeof(OverflowException))]
        public void ProductOfTransformedNullableDecimals_Should_ThrowOnOverflow()
        {
            var decimals = new decimal?[]{ decimal.MaxValue, 10.0M };

            var actual = decimals.Product(x => x);
        }
    }
}
<file_sep>using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Chains;

namespace Chains.Test
{
    [TestClass]
    public class ChunkByTests
    {
        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ChunkBy_Should_ThrowIfSourceIsNull()
        {
            var seq = ((IEnumerable<int>)null).ChunkBy(1).ToList();
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void ChunkBy_Should_ThrowIfSizeIsNegative()
        {
            var seq = new List<int> { 1, 2, 3 };

            var newSeq = seq.ChunkBy(-1).ToList();
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void ChunkBy_Should_ThrowIfSizeIsZero()
        {
            var seq = new List<int> { 1, 2, 3 };

            var newSeq = seq.ChunkBy(0).ToList();
        }

        [TestMethod]
        public void ChunkBy_Should_ReturnAnEmptySequenceIfSourceIsEmpty()
        {
            var seq = Enumerable.Empty<int>();
            var size = 2;

            var actual = seq.ChunkBy(size);

            Assert.IsFalse(!actual.Any(), "Expected empty sequence.");
        }

        [TestMethod]
        public void ChunkBy_Should_CreateChunksNoLargerThanSize()
        {
            var seq = Enumerable.Range(0, 10);
            var size = 2;

            var actual = seq.ChunkBy(size);

            foreach (var item in actual)
            {
                var chunkSize = item.Count();
                Assert.IsTrue(
                    chunkSize <= size,
                    $"Expected chunk size <= {size}, size was {chunkSize}.");
            }
        }

        [TestMethod]
        public void ChunkBy_Should_CreateNoMoreChunksThanNecessary()
        {
            var seq = Enumerable.Range(0, 10);
            var size = 2;
            var numberOfChunks = 5;

            var actual = seq.ChunkBy(size);

            Assert.AreEqual(
                numberOfChunks,
                actual.Count(),
                "Incorrect number of chunks.");
        }

        [TestMethod]
        public void ChunkBy_Should_CreateSmallerChunkIfNotEnoughElementsRemain()
        {
            var seq = Enumerable.Range(0, 10);
            var size = 3;

            var actual = seq.ChunkBy(size);

            Assert.IsTrue(
                actual.Last().Count() < size,
                $"Expected last chunk size strictly less than {size}.");
        }

        [TestMethod]
        public void ChunkBy_Should_NotChangeTheOrderOfElements()
        {
            var seq = Enumerable.Range(0, 10);
            var size = 2;

            var actual = seq.ChunkBy(size).SelectMany(x => x);

            Assert.IsTrue(
                seq.SequenceEqual(actual),
                "Expected sequences to be equal.");
        }
    }
}
<file_sep>using System;
using System.Collections.Generic;
using System.Linq;

namespace Chains
{
    public static partial class Chains
    {
        /// <summary>
        /// Splits elements of an <see cref="IEnumerable{TSource}"/> into chunks
        /// with a given maximum size.
        /// </summary>
        /// <typeparam name="TSource">
        /// The type of the items in the source enumerables.
        /// </typeparam>
        /// <param name="source">The source enumerable to operate on.</param>
        /// <param name="size">The maximum chunk size.</param>
        /// <exception cref="ArgumentNullException">
        /// The source is null.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// The size is not a positive integer.
        /// </exception>
        /// <returns>
        /// An enumerable of enumerables, where each one contains at most `size`
        /// number of elements.
        /// </returns>
        public static IEnumerable<IEnumerable<TSource>> ChunkBy<TSource>(
            this IEnumerable<TSource> source,
            int size)
        {
            source.EnsureNotNull(nameof(source));
            size.Ensure(x => x > 0, nameof(size), "Must be a positive integer.");

            if (!source.Any())
            {
                yield return Enumerable.Empty<TSource>();
                yield break;
            }

            var chunk = source.Take(size);
            var rest = source.Skip(size);

            while (chunk.Any())
            {
                yield return chunk;
                chunk = rest.Take(size);
                rest = rest.Skip(size);
            }
        }
    }
}
<file_sep>using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Chains;

namespace Chains.Test
{
    [TestClass]
    public class ExceptLastTests
    {
        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ExceptLast_Should_ThrowIfSourceIsNull()
        {
            var sequence = (IEnumerable<object>)null;

            var actual = sequence.ExceptLast().ToList();
        }

        [TestMethod]
        [ExpectedException(typeof(InvalidOperationException))]
        public void ExceptLast_Should_ThrowIfSourceIsEmpty()
        {
            var sequence = Enumerable.Empty<object>();

            var actual = sequence.ExceptLast().ToList();
        }

        [TestMethod]
        public void ExceptLast_Should_YieldAllElementsExceptTheLast()
        {
            var sequence = Enumerable.Range(1, 3);
            var expected = new[]{ 1, 2 };

            var actual = sequence.ExceptLast();

            Assert.IsTrue(
                actual.SequenceEqual(expected),
                "Actual and expected sequences did not match.");
        }

        [TestMethod]
        public void ExceptLast_Should_BeTheInverseOfLast()
        {
            var sequence = Enumerable.Range(1, 3);

            var last = sequence.Last();
            var exceptLast = sequence.ExceptLast();
            var concat = exceptLast.Append(last);

            Assert.IsTrue(
                concat.SequenceEqual(sequence),
                "Inverse constraint failed.");
        }

    }
}
<file_sep>using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Chains;

namespace Chains.Test
{
    [TestClass]
    public class DistinctByTests
    {
        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void DistinctBy_Should_ThrowIfSourceIsNull()
        {
            var seq = ((IEnumerable<int>)null).DistinctBy(x => x).ToList();
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void DistinctBy_Should_ThrowIfKeySelectorIsNull()
        {
            var seq = new List<int> { 1, 2, 3 };

            var newSeq = seq.DistinctBy((Func<int, int>)null).ToList();
        }

        [TestMethod]
        public void DistinctBy_Should_SelectValuesByKey()
        {
            var seq = new List<int> { 1, 2, 3, 4 };
            var expected = new List<int> { 1, 2 };

            var actual = seq.DistinctBy(x => x % 2);

            foreach (var item in actual)
                Assert.IsTrue(
                    expected.Contains(item),
                    $"Expected {item} in sequence.");
        }
    }
}
<file_sep>using System;
using System.Collections.Generic;

namespace Chains
{
    public static partial class Chains
    {
        /// <summary>
        /// Creates a histogram of a given <see cref="IEnumerable{T}" />.
        /// </summary>
        /// <typeparam name="TSource">
        /// The type of the items in the source enumerable.
        /// </typeparam>
        /// <param name="source">The source enumerable to operate on.</param>
        /// <exception cref="ArgumentNullException">
        /// The source is null.
        /// </exception>
        /// <returns>
        /// An <see cref="IDictionary{TKey,TValue}" /> mapping each element in
        /// the source enumerable to the number of times it appeared.
        /// </returns>
        public static IDictionary<TSource, int> ToHistogram<TSource>(
            this IEnumerable<TSource> source)
        {
            source.EnsureNotNull(nameof(source));

            var dict = new Dictionary<TSource, int>();

            foreach (var element in source)
            {
                if (!dict.ContainsKey(element))
                {
                    dict[element] = 0;
                }

                dict[element]++;
            }

            return dict;
        }
    }
}
<file_sep>using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Chains;

namespace Chains.Test
{
    [TestClass]
    public class ToHistogramTests
    {
        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ToHistogram_Should_ThrowIfSourceIsNull()
        {
            var seq = ((IEnumerable<int>)null).ToHistogram();
        }

        [TestMethod]
        public void ToHistogram_Should_ReturnAnEmptyDictionaryIfSourceIsEmpty()
        {
            var seq = Enumerable.Empty<int>();

            var actual = seq.ToHistogram();

            Assert.IsFalse(actual.Any(), "Expected empty dictionary.");
        }

        [TestMethod]
        public void ToHistogram_Should_GenerateCountsOfUniqueValues()
        {
            var seq = new[] { 1, 2, 3, 1, 2, 1 };

            var actual = seq.ToHistogram();

            Assert.IsTrue(actual.ContainsKey(1), "Expected key 1 to exist.");
            Assert.AreEqual(actual[1], 3, "Expected 3 occurrences of key 1.");
            Assert.IsTrue(actual.ContainsKey(2), "Expected key 2 to exist.");
            Assert.AreEqual(actual[2], 2, "Expected 2 occurrences of key 2.");
            Assert.IsTrue(actual.ContainsKey(3), "Expected key 3 to exist.");
            Assert.AreEqual(actual[3], 1, "Expected 1 occurence of key 3.");
        }
    }
}
<file_sep>﻿using System;

namespace Chains
{
    /// <summary>
    /// Provides additional extension methods for <see cref="IEnumerable{T}" />
    /// similar to LINQ.
    /// </summary>
    public static partial class Chains
    {
    }
}
<file_sep>using System;
using System.Collections.Generic;
using System.Linq;

namespace Chains
{
    public static partial class Chains
    {
        /// <summary>
        /// Computes the product of a sequence of <see cref="Int32" /> values.
        /// </summary>
        /// <param name="source">The source enumerable to operate on.</param>
        /// <exception cref="ArgumentNullException">
        /// The source is null.
        /// </exception>
        /// <exception cref="OverflowException">
        /// The product exceeds the maximum value.
        /// </exception>
        /// <returns>
        /// The product of the values, or one if the sequence is empty.
        /// </returns>
        public static int Product(this IEnumerable<int> source)
        {
            source.EnsureNotNull(nameof(source));

            return source
                .Aggregate(1, (x, y) => { checked { return x * y; } });
        }

        /// <summary>
        /// Computes the product of a sequence of nullable
        /// <see cref="Int32" /> values, excluding null values.
        /// </summary>
        /// <param name="source">The source enumerable to operate on.</param>
        /// <exception cref="ArgumentNullException">
        /// The source is null.
        /// </exception>
        /// <exception cref="OverflowException">
        /// The product exceeds the maximum value.
        /// </exception>
        /// <returns>
        /// The product of the values, or one if the sequence is empty.
        /// </returns>
        public static int? Product(this IEnumerable<int?> source)
        {
            source.EnsureNotNull(nameof(source));

            return source
                .OfType<int>()
                .Aggregate(1, (x, y) => { checked { return x * y; } });
        }

        /// <summary>
        /// Computes the product of a sequence of <see cref="Int64" /> values.
        /// </summary>
        /// <param name="source">The source enumerable to operate on.</param>
        /// <exception cref="ArgumentNullException">
        /// The source is null.
        /// </exception>
        /// <exception cref="OverflowException">
        /// The product exceeds the maximum value.
        /// </exception>
        /// <returns>
        /// The product of the values, or one if the sequence is empty.
        /// </returns>
        public static long Product(this IEnumerable<long> source)
        {
            source.EnsureNotNull(nameof(source));

            return source
                .Aggregate(1L, (x, y) => { checked { return x * y; } });
        }

        /// <summary>
        /// Computes the product of a sequence of nullable
        /// <see cref="Int64" /> values, excluding null values.
        /// </summary>
        /// <param name="source">The source enumerable to operate on.</param>
        /// <exception cref="ArgumentNullException">
        /// The source is null.
        /// </exception>
        /// <exception cref="OverflowException">
        /// The product exceeds the maximum value.
        /// </exception>
        /// <returns>
        /// The product of the values, or one if the sequence is empty.
        /// </returns>
        public static long? Product(this IEnumerable<long?> source)
        {
            source.EnsureNotNull(nameof(source));

            return source
                .OfType<long>()
                .Aggregate(1L, (x, y) => { checked { return x * y; } });
        }

        /// <summary>
        /// Computes the product of a sequence of <see cref="Single" /> values.
        /// </summary>
        /// <param name="source">The source enumerable to operate on.</param>
        /// <exception cref="ArgumentNullException">
        /// The source is null.
        /// </exception>
        /// <returns>
        /// The product of the values, or one if the sequence is empty.
        /// </returns>
        public static float Product(this IEnumerable<float> source)
        {
            source.EnsureNotNull(nameof(source));

            return source
                .Aggregate(1.0F, (x, y) => x * y);
        }

        /// <summary>
        /// Computes the product of a sequence of nullable
        /// <see cref="Single" /> values, excluding null values.
        /// </summary>
        /// <param name="source">The source enumerable to operate on.</param>
        /// <exception cref="ArgumentNullException">
        /// The source is null.
        /// </exception>
        /// <returns>
        /// The product of the values, or one if the sequence is empty.
        /// </returns>
        public static float? Product(this IEnumerable<float?> source)
        {
            source.EnsureNotNull(nameof(source));

            return source
                .OfType<float>()
                .Aggregate(1.0F, (x, y) => x * y);
        }

        /// <summary>
        /// Computes the product of a sequence of <see cref="Double" /> values.
        /// </summary>
        /// <param name="source">The source enumerable to operate on.</param>
        /// <exception cref="ArgumentNullException">
        /// The source is null.
        /// </exception>
        /// <returns>
        /// The product of the values, or one if the sequence is empty.
        /// </returns>
        public static double Product(this IEnumerable<double> source)
        {
            source.EnsureNotNull(nameof(source));

            return source
                .Aggregate(1.0, (x, y) => x * y);
        }

        /// <summary>
        /// Computes the product of a sequence of nullable
        /// <see cref="Double" /> values, excluding null values.
        /// </summary>
        /// <param name="source">The source enumerable to operate on.</param>
        /// <exception cref="ArgumentNullException">
        /// The source is null.
        /// </exception>
        /// <returns>
        /// The product of the values, or one if the sequence is empty.
        /// </returns>
        public static double? Product(this IEnumerable<double?> source)
        {
            source.EnsureNotNull(nameof(source));

            return source
                .OfType<double>()
                .Aggregate(1.0, (x, y) => x * y);
        }

        /// <summary>
        /// Computes the product of a sequence of <see cref="Decimal" /> values.
        /// </summary>
        /// <param name="source">The source enumerable to operate on.</param>
        /// <exception cref="ArgumentNullException">
        /// The source is null.
        /// </exception>
        /// <exception cref="OverflowException">
        /// The product exceeds the maximum value.
        /// </exception>
        /// <returns>
        /// The product of the values, or one if the sequence is empty.
        /// </returns>
        public static decimal Product(this IEnumerable<decimal> source)
        {
            source.EnsureNotNull(nameof(source));

            return source
                .Aggregate(1.0M, (x, y) => { checked { return x * y; } });
        }

        /// <summary>
        /// Computes the product of a sequence of nullable
        /// <see cref="Decimal" /> values, excluding null values.
        /// </summary>
        /// <param name="source">The source enumerable to operate on.</param>
        /// <exception cref="ArgumentNullException">
        /// The source is null.
        /// </exception>
        /// <exception cref="OverflowException">
        /// The product exceeds the maximum value.
        /// </exception>
        /// <returns>
        /// The product of the values, or one if the sequence is empty.
        /// </returns>
        public static decimal? Product(this IEnumerable<decimal?> source)
        {
            source.EnsureNotNull(nameof(source));

            return source
                .OfType<decimal>()
                .Aggregate(1.0M, (x, y) => { checked { return x * y; } });
        }

        /// <summary>
        /// Computes the product of a sequence of <see cref="Int32" /> values,
        /// obtained by invoking a transform function on the elements of the
        /// input sequence.
        /// </summary>
        /// <typeparam name="TSource">
        /// The type of the items in the source enumerable.
        /// </typeparam>
        /// <param name="source">The source enumerable to operate on.</param>
        /// <param name="selector">The transformation function to use.</param>
        /// <exception cref="ArgumentNullException">
        /// The source is null.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// The selector is null.
        /// </exception>
        /// <exceptuon cref="OverflowException">
        /// The product exceeds the maximum value.
        /// </exception>
        /// <returns>
        /// The product of the projected values, or one if the sequence is
        /// empty.
        /// </returns>
        public static int Product<TSource>(
            this IEnumerable<TSource> source,
            Func<TSource, int> selector)
        {
            source.EnsureNotNull(nameof(source));
            selector.EnsureNotNull(nameof(selector));

            return source
                .Select(selector)
                .Aggregate(1, (x, y) => { checked { return x * y; } });
        }

        /// <summary>
        /// Computes the product of a sequence of nullable <see cref="Int32" />
        /// values, obtained by invoking a transform function on the elements
        /// of the input sequence, excluding null values.
        /// </summary>
        /// <typeparam name="TSource">
        /// The type of the items in the source enumerable.
        /// </typeparam>
        /// <param name="source">The source enumerable to operate on.</param>
        /// <param name="selector">The transformation function to use.</param>
        /// <exception cref="ArgumentNullException">
        /// The source is null.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// The selector is null.
        /// </exception>
        /// <exceptuon cref="OverflowException">
        /// The product exceeds the maximum value.
        /// </exception>
        /// <returns>
        /// The product of the projected values, or one if the sequence is
        /// empty.
        /// </returns>
        public static int? Product<TSource>(
            this IEnumerable<TSource> source,
            Func<TSource, int?> selector)
        {
            source.EnsureNotNull(nameof(source));
            selector.EnsureNotNull(nameof(selector));

            return source
                .Select(selector)
                .OfType<int>()
                .Aggregate(1, (x, y) => { checked { return x * y; } });
        }

        /// <summary>
        /// Computes the product of a sequence of <see cref="Int64" /> values,
        /// obtained by invoking a transform function on the elements of the
        /// input sequence.
        /// </summary>
        /// <typeparam name="TSource">
        /// The type of the items in the source enumerable.
        /// </typeparam>
        /// <param name="source">The source enumerable to operate on.</param>
        /// <param name="selector">The transformation function to use.</param>
        /// <exception cref="ArgumentNullException">
        /// The source is null.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// The selector is null.
        /// </exception>
        /// <exceptuon cref="OverflowException">
        /// The product exceeds the maximum value.
        /// </exception>
        /// <returns>
        /// The product of the projected values, or one if the sequence is
        /// empty.
        /// </returns>
        public static long Product<TSource>(
            this IEnumerable<TSource> source,
            Func<TSource, long> selector)
        {
            source.EnsureNotNull(nameof(source));
            selector.EnsureNotNull(nameof(selector));

            return source
                .Select(selector)
                .Aggregate(1L, (x, y) => { checked { return x * y; } });
        }

        /// <summary>
        /// Computes the product of a sequence of nullable <see cref="Int64" />
        /// values, obtained by invoking a transform function on the elements
        /// of the input sequence, excluding null values.
        /// </summary>
        /// <typeparam name="TSource">
        /// The type of the items in the source enumerable.
        /// </typeparam>
        /// <param name="source">The source enumerable to operate on.</param>
        /// <param name="selector">The transformation function to use.</param>
        /// <exception cref="ArgumentNullException">
        /// The source is null.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// The selector is null.
        /// </exception>
        /// <exceptuon cref="OverflowException">
        /// The product exceeds the maximum value.
        /// </exception>
        /// <returns>
        /// The product of the projected values, or one if the sequence is
        /// empty.
        /// </returns>
        public static long? Product<TSource>(
            this IEnumerable<TSource> source,
            Func<TSource, long?> selector)
        {
            source.EnsureNotNull(nameof(source));
            selector.EnsureNotNull(nameof(selector));

            return source
                .Select(selector)
                .OfType<long>()
                .Aggregate(1L, (x, y) => { checked { return x * y; } });
        }

        /// <summary>
        /// Computes the product of a sequence of <see cref="Single" /> values,
        /// obtained by invoking a transform function on the elements of the
        /// input sequence.
        /// </summary>
        /// <typeparam name="TSource">
        /// The type of the items in the source enumerable.
        /// </typeparam>
        /// <param name="source">The source enumerable to operate on.</param>
        /// <param name="selector">The transformation function to use.</param>
        /// <exception cref="ArgumentNullException">
        /// The source is null.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// The selector is null.
        /// </exception>
        /// <returns>
        /// The product of the projected values, or one if the sequence is
        /// empty.
        /// </returns>
        public static float Product<TSource>(
            this IEnumerable<TSource> source,
            Func<TSource, float> selector)
        {
            source.EnsureNotNull(nameof(source));
            selector.EnsureNotNull(nameof(selector));

            return source
                .Select(selector)
                .Aggregate(1.0F, (x, y) => x * y);
        }

        /// <summary>
        /// Computes the product of a sequence of nullable <see cref="Single" />
        /// values, obtained by invoking a transform function on the elements
        /// of the input sequence, excluding null values.
        /// </summary>
        /// <typeparam name="TSource">
        /// The type of the items in the source enumerable.
        /// </typeparam>
        /// <param name="source">The source enumerable to operate on.</param>
        /// <param name="selector">The transformation function to use.</param>
        /// <exception cref="ArgumentNullException">
        /// The source is null.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// The selector is null.
        /// </exception>
        /// <returns>
        /// The product of the projected values, or one if the sequence is
        /// empty.
        /// </returns>
        public static float? Product<TSource>(
            this IEnumerable<TSource> source,
            Func<TSource, float?> selector)
        {
            source.EnsureNotNull(nameof(source));
            selector.EnsureNotNull(nameof(selector));

            return source
                .Select(selector)
                .OfType<float>()
                .Aggregate(1.0F, (x, y) => x * y);
        }

        /// <summary>
        /// Computes the product of a sequence of <see cref="Double" /> values,
        /// obtained by invoking a transform function on the elements of the
        /// input sequence.
        /// </summary>
        /// <typeparam name="TSource">
        /// The type of the items in the source enumerable.
        /// </typeparam>
        /// <param name="source">The source enumerable to operate on.</param>
        /// <param name="selector">The transformation function to use.</param>
        /// <exception cref="ArgumentNullException">
        /// The source is null.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// The selector is null.
        /// </exception>
        /// <returns>
        /// The product of the projected values, or one if the sequence is
        /// empty.
        /// </returns>
        public static double Product<TSource>(
            this IEnumerable<TSource> source,
            Func<TSource, double> selector)
        {
            source.EnsureNotNull(nameof(source));
            selector.EnsureNotNull(nameof(selector));

            return source
                .Select(selector)
                .Aggregate(1.0, (x, y) => x * y);
        }

        /// <summary>
        /// Computes the product of a sequence of nullable <see cref="Double" />
        /// values, obtained by invoking a transform function on the elements
        /// of the input sequence, excluding null values.
        /// </summary>
        /// <typeparam name="TSource">
        /// The type of the items in the source enumerable.
        /// </typeparam>
        /// <param name="source">The source enumerable to operate on.</param>
        /// <param name="selector">The transformation function to use.</param>
        /// <exception cref="ArgumentNullException">
        /// The source is null.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// The selector is null.
        /// </exception>
        /// <returns>
        /// The product of the projected values, or one if the sequence is
        /// empty.
        /// </returns>
        public static double? Product<TSource>(
            this IEnumerable<TSource> source,
            Func<TSource, double?> selector)
        {
            source.EnsureNotNull(nameof(source));
            selector.EnsureNotNull(nameof(selector));

            return source
                .Select(selector)
                .OfType<double>()
                .Aggregate(1.0, (x, y) => x * y);
        }

        /// <summary>
        /// Computes the product of a sequence of <see cref="Decimal" /> values,
        /// obtained by invoking a transform function on the elements of the
        /// input sequence.
        /// </summary>
        /// <typeparam name="TSource">
        /// The type of the items in the source enumerable.
        /// </typeparam>
        /// <param name="source">The source enumerable to operate on.</param>
        /// <param name="selector">The transformation function to use.</param>
        /// <exception cref="ArgumentNullException">
        /// The source is null.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// The selector is null.
        /// </exception>
        /// <exceptuon cref="OverflowException">
        /// The product exceeds the maximum value.
        /// </exception>
        /// <returns>
        /// The product of the projected values, or one if the sequence is
        /// empty.
        /// </returns>
        public static decimal Product<TSource>(
            this IEnumerable<TSource> source,
            Func<TSource, decimal> selector)
        {
            source.EnsureNotNull(nameof(source));
            selector.EnsureNotNull(nameof(selector));

            return source
                .Select(selector)
                .Aggregate(1.0M, (x, y) => { checked { return x * y; } });
        }

        /// <summary>
        /// Computes the product of a sequence of nullable
        /// <see cref="Decimal" /> values, obtained by invoking a transform
        /// function on the elements of the input sequence, excluding null
        /// values.
        /// </summary>
        /// <typeparam name="TSource">
        /// The type of the items in the source enumerable.
        /// </typeparam>
        /// <param name="source">The source enumerable to operate on.</param>
        /// <param name="selector">The transformation function to use.</param>
        /// <exception cref="ArgumentNullException">
        /// The source is null.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// The selector is null.
        /// </exception>
        /// <exceptuon cref="OverflowException">
        /// The product exceeds the maximum value.
        /// </exception>
        /// <returns>
        /// The product of the projected values, or one if the sequence is
        /// empty.
        /// </returns>
        public static decimal? Product<TSource>(
            this IEnumerable<TSource> source,
            Func<TSource, decimal?> selector)
        {
            source.EnsureNotNull(nameof(source));
            selector.EnsureNotNull(nameof(selector));

            return source
                .Select(selector)
                .OfType<decimal>()
                .Aggregate(1.0M, (x, y) => { checked { return x * y; } });
        }
    }
}
<file_sep>using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Chains;

namespace Chains.Test
{
    [TestClass]
    public class CycleTests
    {
        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Cycle_Should_ThrowIfSourceIsNull()
        {
            var sequence = (IEnumerable<object>)null;

            var actual = sequence.Cycle().ToList();
        }

        [TestMethod]
        public void Cycle_Should_YieldTheElementsOfAnEnumerableInfinitely()
        {
            var sequence = Enumerable.Range(1, 3);
            var count = 5;
            var expected = new[]{ 1, 2, 3, 1, 2 };

            var actual = sequence.Cycle().Take(count);

            Assert.IsTrue(
                actual.SequenceEqual(expected),
                "Actual and expected sequences did not match.");
        }

        [TestMethod]
        public void Cycle_Should_YieldNothingForAnEmptyEnumerable()
        {
            var sequence = Enumerable.Empty<object>();

            var actual = sequence.Cycle();

            Assert.IsFalse(actual.Any(), "Expected empty sequence.");
        }
    }
}
<file_sep>using System.Runtime.CompilerServices;

[assembly:InternalsVisibleTo("Chains.Test")]
