Little Gems of the Enumerable Class: Empty, Range, and Repeat

If you're doing any non-trivial amount of work with C# and .NET, you'll be familiar with the joy that is LINQ and the set of extension method it provides. While the Select, Where, Any, and more extension methods are generally well known, the Enumerable static class also offers three non-extension methods, namely Empty<T>, Range, and Repeat<T>, which I want to highlight in this post.

Returning an Empty Collection: Enumerable.Empty<T>

The Enumerable.Empty<T> method returns an empty enumerable which doesn't yield any values when being enumerated. Enumerable.Empty<T> comes in very handy when you want to pass an empty to collection to a method accepting a parameter of type IEnumerable<T>.

What's the type that's being used by Enumerable.Empty<int> internally? Let's find out:

Enumerable.Empty<int>().GetType() // "System.Int32[]"

We can see that the returned sequence is an (empty) array of integers.

For performance reasons, the returned array instance is cached for every type T, which allows us to observe the following behavior:

Enumerable.Empty<int>() == Enumerable.Empty<int>()    // True
Enumerable.Empty<int>() == Enumerable.Empty<string>() // False

A reference comparison via == obviously returns false for the two different arrays. The SequenceEqual method, however, returns true since neither sequence yields a value:

IEnumerable<object> integers = Enumerable.Empty<int>().Cast<object>();
IEnumerable<object> strings = Enumerable.Empty<string>();

bool equalByReference = integers == strings;            // False
bool equalBySequence = integers.SequenceEqual(strings); // True

Generating Sequential Integers: Enumerable.Range

Some programming languages offer a shorthand notation to create a list of consecutive integers. The following code shows how this can be achieved in Haskell:

[1..5] == [1,2,3,4,5] -- True
[2..5] == [2,3,4,5]   -- True

While C# doesn't define an operator similar to .., the .NET Framework offers the static Enumerable.Range method. It accepts two int parameters, start and count, and constructs a sequence of count consecutive integers, starting at start:

IEnumerable<int> numbers = Enumerable.Range(1, 5);
string numberList = string.Join(",", numbers); // "1,2,3,4,5"

Note that the second parameter is the number of integers to generate, not the inclusive upper bound of the range. This is where the resulting sequence differs from the one created by Haskell's list construction syntax:

IEnumerable<int> numbers = Enumerable.Range(2, 5);
string numberList = string.Join(",", numbers); // "2,3,4,5,6"

Here's how you could use Range to generate a string containing the English alphabet:

IEnumerable<char> letters = Enumerable
    .Range(0, 26)
    .Select(x => (char)(x + 'a'));

string alphabet = string.Join("", letters); // "abcdefghijklmnopqrstuvwxyz"

The Enumerable.Range method will throw an ArgumentOutOfRangeException if either count is negative or start + count - 1 is larger than int.MaxValue.

Repeating an Element: Enumerable.Repeat<T>

The third and last method I want to address in this post is Enumerable.Repeat<T>. Its signature is Repeat<T>(T element, int count), and it creates a sequence with exactly count occurences of the specified element:

IEnumerable<int> ones = Enumerable.Repeat(1, 5);
string numberList = string.Join(",", ones); // "1,1,1,1,1"

While Enumerable.Repeat was conceptualized to create a sequence of a repeated value, it can be used as a driver for a generator function as well. The following snippet generates ten (pseudo-)random numbers between 0 and 1:

var random = new Random();
IEnumerable<double> randomNumbers = Enumerable
    .Repeat(0, 10)
    .Select(_ => random.NextDouble());

Notice that the selected value _ isn't used at all. I like to explicitly denote that by using an underscore for the variable name. Instead, Enumerable.Repeat is only used to repeat the number generation 10 times.

Like most methods in the System.Linq namespace, Enumerable.Repeat<T> is lazy by design to avoid computing unnecessary values: Instead of immediately returning an entirely precomputed range sequence, it returns an iterator which yields values until either the range is exhausted or the caller stops enumerating it.

Looking for More LINQ?

I encourage you to also check out ExtraLINQ, an open source project of mine which is available on NuGet, and morelinq, a library written by Jon Skeet. Both add a variety of helpful extension methods to your LINQ utility belt.

Similar Posts:

Use the coupon code LAUNCHDAY for $10 off!

Learn React

Comments