4 Comments

IEnumerable, for a More Elegant C#

I’ve recently had the chance to dig in deeper than ever before in C#. There are a lot of things I like about C#, one of them being LINQ (Language-Integrated Query). I’ve started to rely heavily on one aspect of LINQ, Enumerables.

What is LINQ?

At first glance, LINQ expressions look like SQL queries. Expressions include keywords like from, where, and select. To filter out all odd numbers in a list, you could construct a query like the following.

var numbers = new List <int> {1, 3, 5, 2, 0, 8};
 
var query = 
  from number in numbers
  where (number % 2 == 0)
  select number;

The above syntax is a step up from traditional foreach loops. The LINQ syntax is more declarative than the prior. The declarative nature makes the code’s purpose clear. Great, but can it be improved more?

Intro to IEnumerable

I discovered a little gem that makes data transformations very composable. As you will see below, the result of one query can often be passed directly into the next.

It all starts with the IEnumerable interface. IEnumerable adds a bunch of handy methods to classes that implement it including Select, Zip, and Where. Lucky for us, one class that implements IEnumerable is List. Let’s take a look at the above example written with IEnumerable extensions.

var numbers = new List<int>{1, 3, 5, 2, 0, 8};
var evens = numbers.Where(num => num % 2 == 0);

A trivial example, but you can already start to see how this can be powerful.

Using IEnumerable

Now let’s take it one step farther. Let’s say we have a Table class. It looks like this:

public class Table
{
  public string Title {get; set}
  public List<Row> Rows {get; set;}
}

It uses a Row class that looks like this.

public class Row 
{
  public string Title {get; set;}
  public bool TitleRow {get; set;}
  public List<Cell> Cells {get; set;}
}

And the Row uses a Cell class that looks like this.

public class Cell 
{
  public string Label {get; set;}
  public bool IsNumber {get; set;}
}

Our goal is to return a list of all cells that are part of a title row (where a table can have multiple title rows). First, the traditional method.

var titleCells = new List<Cell>();
foreach(var row in Table.Rows) 
{
  if(row.TitleRow)
  {
    foreach(var cell in row.Cells)
    {
      titleCells.Add(cell);
    }
  }
}
 
return titleCells;

Now let’s look at the same example using Enumerables.

return Table.Rows
  .Where(row => row.TitleRow)
  .Select(row => row.Cells)
  .SelectMany(x => x);

Easy Extensions

In this example we introduce SelectMany. If we just returned the Select, we would have an IEnumerable<IEnumerable<Cell>>, SelectMany allows us to flatten that structure. That sounds like something we might want to do more than once. In order to give it a more meaningful name and allow for reuse, we can break it out into an extension method.

public static IEnumerable<TSource> Flatten<TSource>(this IEnumerable<IEnumerable<TSource>> source)
{
  return source.SelectMany(x => x);
}

Now, whenever we want to flatten a list, we can call Flatten().

Putting it All Together

Let’s go back to the previous example. Say the specification changed, and we now want cells that are part of a title row and are a number. This is how we’d deal with that using traditional C#.

var titleCells = new List<Cell>();
foreach(var row in Table.Rows) 
{
  if(row.TitleRow)
  {
    foreach(var cell in row.Cells)
    {
      if (cell.IsNumber)
      {
        titleCells.Add(cell);
      }
    }
  }
}
 
return titleCells;

We had to add an additional if branch, bumping our code farther over and adding visual complexity. Compare that to the following IEnumerable implementation.

return Table.Rows
  .Where(row => row.TitleRow)
  .Select(row => row.Cells.Where(cell => cell.IsNumber))
  .Flatten();

We add a Select statement that will select all of the cells that pass the Where clause and then Flatten that list.

Conclusion

You start to get a good idea of how IEnumerables keep code elegant, easy to read, and easy to maintain. Extend IEnumerables farther and I think you’ll start to see why LINQ and IEnumerables are the way to go in C#!