Linq: SelectMany

Windows Phone From Scratch #47

A LINQ Tutorial

SelectMany is a crucial operator in both LINQ and in Reactive Extensions.  It may be SelectMany easier to see what it does by focusing on LINQ, as most of us are more familiar with querying against IEnumerables.

The primary job of SelectMany is to “flatten” a list of lists.

A practical example of this would occur if you had a collection of books, each of which had one or more authors and your goal was to obtain a list of all the authors of all the books.

Using Select would get you a collection of lists, each list containing one or more authors, but what you want is not a collection of lists but rather, a collection of Authors.

To see this at work, create a new Windows Phone application.

On the main page place a single list box,

<Grid
   x:Name="ContentPanel"
   Grid.Row="1"
   Margin="12,0,12,0">

   <ListBox
      Name="Output"
      HorizontalAlignment="Stretch"
      VerticalAlignment="Stretch"
      Margin="10" />
</Grid>

We’ll need a very simple Book class,

public class Book
{
   public String Title { get; set; }
   public List<Author> Authors { get; set; }
   public int NumPages { get; set; }
}

Notice that the Authors property is a List<Author>.  We therefore need an Author class,

public class Author
{
   public string FirstName;
   public string LastName;
}

We can now generate a list of books. Each book will need a list of authors, a title and the number of pages in the book,

private void CreateBooks( )
{
   Books = new List<Book>( );
   Author auth1 = new Author( ) { FirstName = "Jesse", LastName = "Liberty" };
   Author auth2 = new Author( ) { FirstName = "Ian", LastName = "Griffiths" };
   Author auth3 = new Author( ) { FirstName = "Matthew", LastName = "Adams" };

   List<Author> authors = new List<Author>( ) { auth1, auth2, auth3 };
   Book newBook = new Book( ) { Authors = authors, NumPages = 500, Title = "Programming C#" };
   Books.Add( newBook );

   auth1 = new Author( ) { FirstName = "John", LastName = "Galloway" };
   authors = new List<Author>( ) { auth1 };
   newBook = new Book( ) { Authors = authors, NumPages = 350, Title = "Book 2" };
   Books.Add( newBook );

   auth1 = new Author( ) { FirstName = "Jesse", LastName = "Liberty" };
   auth2 = new Author( ) { FirstName = "Paul", LastName = "Betts" };
   authors = new List<Author>( ) { auth1, auth2 };
   newBook = new Book( ) { Authors = authors, NumPages = 375, Title = "Programming with Rx" };
   Books.Add( newBook );
}

With this data in place, representing data that might be retrieved from a database, we are ready to make our query, with a goal of getting a list of all the last names of all the authors of all the books.

We can do this with either Select or SelectMany. Select will return a collection of the authors for all the books, but that IEnumerable will be an IEnumerable of List of authors (since each book has a List of authors).  That’s fine, if a bit cumbersome:

 IEnumerable<List<Author>> EnumerableOfListOfAuthors = Books
    .Select( book => book.Authors );
 foreach ( var listOfAuthors in EnumerableOfListOfAuthors )
 {
    foreach ( Author auth in listOfAuthors )
    {
       Output.Items.Add( auth.LastName );
    }
 }

Because we have an enumerable of a list, we need an outer and an inner foreach loop so that we can get the last names of all the authors.

What we’d really like, though, instead of an Enumerable of a List, is just an Enumerable of Authors…. and that is what SelectMany is for,

IEnumerable<Author> authors = Books
   .SelectMany( book => book.Authors );
foreach ( Author auth in authors )
{
   Output.Items.Add( auth.LastName );
}

The output of both sets of code is identical (the list of all the authors) but the latter is far simpler and thus easier to understand and maintain.

About Jesse Liberty

Jesse Liberty has three decades of experience writing and delivering software projects and is the author of 2 dozen books and a couple dozen online courses. His latest book, Building APIs with .NET will be released early in 2025. Liberty is a Senior SW Engineer for CNH and he was a Senior Technical Evangelist for Microsoft, a Distinguished Software Engineer for AT&T, a VP for Information Services for Citibank and a Software Architect for PBS. He is a Microsoft MVP.
This entry was posted in Data, Linq, Mini-Tutorial, Patterns & Skills and tagged , . Bookmark the permalink.

6 Responses to Linq: SelectMany

Comments are closed.