Programming Reactive Extensions and LINQ 1st Edition Jesse Liberty
Programming Reactive Extensions and LINQ 1st Edition Jesse Liberty
Programming Reactive Extensions and LINQ 1st Edition Jesse Liberty
Programming Reactive Extensions and LINQ 1st Edition Jesse Liberty
1. Visit https://ebookfinal.com to download the full version and
explore more ebooks
Programming Reactive Extensions and LINQ 1st
Edition Jesse Liberty
_____ Click the link below to download _____
https://ebookfinal.com/download/programming-reactive-
extensions-and-linq-1st-edition-jesse-liberty/
Explore and download more ebooks at ebookfinal.com
2. Here are some suggested products you might be interested in.
Click the link to download
Learning C 3 0 3rd Edition Jesse Liberty
https://ebookfinal.com/download/learning-c-3-0-3rd-edition-jesse-
liberty/
Programming Microsoft LINQ in NET Framework 4 1st Edition
Pialorsi
https://ebookfinal.com/download/programming-microsoft-linq-in-net-
framework-4-1st-edition-pialorsi/
Learning ASP NET 2 0 with Ajax 1st ed Edition Jesse
Liberty
https://ebookfinal.com/download/learning-asp-net-2-0-with-ajax-1st-ed-
edition-jesse-liberty/
Reactive Programming with RxJS Untangle Your Asynchronous
JavaScript Code 1st Edition Sergi Mansilla
https://ebookfinal.com/download/reactive-programming-with-rxjs-
untangle-your-asynchronous-javascript-code-1st-edition-sergi-mansilla/
3. Beginning Java 8 APIs Extensions and Libraries Swing
JavaFX JavaScript JDBC and Network Programming APIs 1st
Edition Kishori Sharan
https://ebookfinal.com/download/beginning-java-8-apis-extensions-and-
libraries-swing-javafx-javascript-jdbc-and-network-programming-
apis-1st-edition-kishori-sharan/
Sams Teach Yourself C in 24 Hours Complete Starter Kit 3rd
Edition Sams Teach Yourself in 24 Hours Jesse Liberty
https://ebookfinal.com/download/sams-teach-yourself-c-in-24-hours-
complete-starter-kit-3rd-edition-sams-teach-yourself-in-24-hours-
jesse-liberty/
LINQ For Dummies John Paul Mueller
https://ebookfinal.com/download/linq-for-dummies-john-paul-mueller/
Lotman and Cultural Studies Encounters and Extensions 1st
Edition Andreas Schonle
https://ebookfinal.com/download/lotman-and-cultural-studies-
encounters-and-extensions-1st-edition-andreas-schonle/
LINQ for Visual C 2008 1st Edition Fabio Claudio
Ferracchiati
https://ebookfinal.com/download/linq-for-visual-c-2008-1st-edition-
fabio-claudio-ferracchiati/
5. Programming Reactive Extensions and LINQ 1st Edition
Jesse Liberty Digital Instant Download
Author(s): Jesse Liberty, Paul Betts
ISBN(s): 9781430237471, 1430237473
Edition: 1
File Details: PDF, 6.14 MB
Year: 2011
Language: english
7. For your convenience Apress has placed some of the front
matter material after the index. Please use the Bookmarks
and Contents at a Glance links to access them.
8. iv
Contents at a Glance
About the Authors......................................................................................................xi
About the Technical Reviewer..................................................................................xii
Acknowledgments...................................................................................................xiii
Foreword ................................................................................................................. xiv
Introduction............................................................................................................. xvi
■Chapter 1: Introducing LINQ and Rx ........................................................................1
■Chapter 2: Core LINQ .............................................................................................19
■Chapter 3: Core Rx.................................................................................................41
■Chapter 4: Practical Rx..........................................................................................57
■Chapter 5: Inside Rx ..............................................................................................77
■Chapter 6: LINQ to SQL...........................................................................................91
■Chapter 7: Reactive Extensions for JavaScript ...................................................111
■Chapter 8: ReactiveUI ..........................................................................................125
■Chapter 9: Testing With Rx ..................................................................................145
Index.......................................................................................................................153
9. xvi
Introduction
Right now, we as programmers are at an impasse—a transition period between the well-understood
world of imperative programming, and a world that is increasingly at odds with this model. In the ’80s,
everything was simple: one machine, one thread, no network.
CPUs are now scaling horizontally, adding more and more cores, instead of scaling the CPU speeds.
Network and disk performance are now increasingly requiring asynchronous I/O in order to build high-
performance solutions.
Our tools to write asynchronous code, however, haven’t kept up with the rest of the world—threads,
locks, and events are the assembly language of asynchronous programming. They are straightforward to
understand, but as the complexity of the application becomes larger, it becomes extremely difficult to
determine if a given block of code will run correctly with respect to the rest of the application.
We need a way to retain the asynchronous nature of modern applications, while also retaining the
deterministic nature of traditional imperative programming.
A compelling solution to these problems is functional reactive programming. This term sounds
academic and overwhelming, however, if you’ve ever written a formula in Excel, good news—you’ve
already done functional reactive programming.
Let’s go through that title piece by piece, starting with the word “functional.” Many people have a
definition of functional programming, often from their college computer science course that covered
Scheme or Common Lisp, usually something along the lines of, “it means never using variables.”
Instead, think of FP as a mindset: “How is the output of my program related to my input, and how
can I describe that relation in code?” In this book, we’ll examine Language Integrated Queries (LINQ), a
powerful technology that allows us to describe a result list based on an input list and a set of
transformations on that input.
The disadvantage to LINQ is that this technology only works on lists, not data that is incoming or
changing. However, there is a type of list that is hiding in plain sight, only disguised—events. Consider
the KeyUp event: for every key that the user presses, an object representing the key will be generated—
“H”–“e”–“l”–“l”–“o”.
What if we thought of an event as a list? What if we could apply everything we know about lists to
events, like how to filter them or create new lists based on existing lists?
Reactive Extensions allow you to treat asynchronous sources of information, such as events, and
reason about them in the same way that you currently can reason about lists. This means, that once you
become proficient with using LINQ to write single-threaded programs, you can apply your knowledge
and write similar programs that are completely asynchronous.
A warning: this book is not easy to understand on first grasp. Rx will stretch your brain in ways it’s
not used to stretching, and you, like the authors, will almost certainly hit a learning curve.
The results, however, are most definitely worth it, as programs that would be incredibly difficult to write
correctly otherwise, are trivially easy to write and reason with Reactive Extensions.
10. C H A P T E R 1
1
Introducing LINQ and Rx
In a sentence, LINQ is a powerful way to interact with and extract data from collections, while Rx is an
extension of LINQ targeted at asynchronous collections; that is, collections that will be populated
asynchronously, typically from web services or elsewhere in the cloud.
LINQ was introduced to C# programmers with C# 3.0. On the other hand, Reactive Extensions (Rx)
is an emerging technology, born in Microsoft DevLabs and adopted as part of a standalone full product
from Microsoft in 2011.
We believe that the use of LINQ will expand greatly in the next few years, especially in conjunction
with Reactive Extensions. Rx extends many of the principles and features of LINQ to a wide set of
platforms (including JavaScript) and will be a vital part of Windows Phone programming, where
asynchronous events are the rule. As the data we deal with becomes more complex, LINQ will help bring
order and clarity to .NET programming. Similarly, as more and more data is retrieved asynchronously
from the cloud, and elsewhere, Rx will help you create simpler, cleaner, and more maintainable code.
In this chapter you will learn why LINQ and Rx are important and you’ll examine some of the
fundamental operators of both LINQ and Rx. This will provide a context for the coming chapters in
which we’ll dive deeper into both LINQ and Reactive Extensions.
■ Note Throughout this book we use Rx as shorthand for Reactive Extensions, just as we use LINQ for Language
Integrated Query.
What LINQ Is
LINQ (Language Integrated Query)—pronounced “link”—extends .NET to provide a way to query and
transform collections, relational data, and XML documents. It provides a SQL-like syntax within C# for
querying data, regardless of where that data originates.
LINQ also brings a much more declarative or functional approach to programming than previously
available in .NET.
11. CHAPTER 1 ■ INTRODUCING LINQ AND RX
2
■ Note Functional programming is one approach to creating declarative code.
In declarative programming, the logic and requirements are expressed but the execution steps
are not.
LINQ and Rx are functional aspects of the C# language, in that they focus more on what you are
trying to accomplish than on the steps required to get to that goal. Most programmers find that the
functional approach makes it cleaner and easier to maintain code than the more traditional, imperative
style of programming.
With the imperative approach, a program consists of a series of steps—often steps within loops—
that detail what to do at any given moment. In declarative programming, we’re able to express what is
required at a higher level of abstraction.
It is the difference between saying on the one hand (imperative): “Open this collection, take each
person out, test to see if the person is a manager, if the person is a manager—increase the salary by 20
percent, and put the person back into the collection;” and, on the other hand saying (declarative): “Raise
the salary of all the managers by 20 percent.”
The imperative style tells you how to accomplish the goal; the declarative style tells you what the
goal is. Using concrete programming examples of Rx and LINQ, this book will demonstrate differences
that are more declarative than imperative.
What Rx Is
Reactive Extensions provide you with a new way to orchestrate and integrate asynchronous events, such
as coordinating multiple streams as they arrive asynchronously from the cloud. With Rx you can
“flatten” these streams into a single method, enormously simplifying your code. For example, the classic
async pattern in .NET is to initiate each call with a BeginXXX method and end it with an EndXXX
method, also known as the Begin/End pattern. If you make more than a few simultaneous asynchronous
calls, following the thread of control becomes impossible very quickly. But with Rx, the Begin/End
pattern is collapsed into a single method, making the code much cleaner and easier to follow.
Reactive Extensions have been described as a library for composing asynchronous and event-based
programs using observable collections.1
Let’s take a closer look at each of these attributes.
• Composing: One of the primary goals of Reactive Extensions is to make the work
of combining asynchronous operations significantly easier. To do this, the data
flow must be clarified and the code consolidated so that it is less spread out
throughout your application.
• Asynchronous: While not everything you’ll do in Rx is asynchronous, the async
code you write will be simpler, easier to understand, and thus far easier to
maintain.
1
MSDN Data Developer Center, “Reactive Extensions,” http://msdn.microsoft.com/
en-us/data/gg577609.
12. CHAPTER 1 ■ INTRODUCING LINQ AND RX
3
• Event-based: Rx can simplify even traditional event-based programs, as you’ll see
later in the book when we examine implementation of drag and drop.
• Observable collections: The bread and butter of Rx. An observable collection is a
collection whose objects may not be available at the time you first interact with it.
We will return to this topic throughout this book. For now, the most useful
analogy is that observable collections are to Rx what enumerable collections are to
LINQ.
■ Caution There is significant potential for confusion between the Silverlight ObservableCollection and
Observable Collections in Rx. The former is a specific generic collection and the latter is any collection that
implements IObservable, which will be described in detail later in this book.
Getting Rx and LINQ
If you have .NET 4 or later, you have LINQ—it is built into the framework. Similarly, if you have the
Windows Phone tools installed, you have Rx for Windows Phone. To obtain the latest edition of Rx for
other .NET platforms, you can go to the Rx site (http://msdn.microsoft.com/en-us/data/gg577610) on
MSDN. There you will find complete instructions for downloading and installing Rx for Phone,
Silverlight, WPF (Windows Presentation Foundation), Xbox, Zune, and JavaScript.
You can also install Rx on a project basis in Visual Studio 2010 (and above) using NuGet. For more
information, visit the NuGet project page at http://nuget.codeplex.com/.
Distinguishing Rx and LINQ
LINQ is brilliant at working with static collections, allowing you to use a SQL-like syntax to query and
manipulate data from disparate sources. On the other hand, Rx’s forté is in working with what we call
future collections—that is collections that have been defined, but not yet fully populated.
LINQ requires that all the data be available when we first start writing our LINQ statements. That is,
to build a new collection with LINQ, we need a starting collection.
What happens, however, if we don’t have all the data? Imagine trying to write a service that operates
on live, stock-trading data, in which case we want to operate on incoming streams of data in real-time.
LINQ would not know what to do with this, as a stream is not a static collection—its contents will arrive,
but they’re not there yet, and the timing of their arrival is not predictable. Rx allows us to operate on this
kind of data just as easily as we can operate on static data with LINQ.
There is a cognitive boot-strap problem for anyone learning Reactive Extensions, just as there is
with LINQ. At first, the Rx code appears confusing and convoluted, and thus difficult to understand and
maintain. Once you become comfortable with the syntax and the “mind set,” however, the code will
seem simpler than “traditional” programming; and many complex problems devolve to rather simple
solutions.
The comparison between LINQ and Rx is not coincidental; these are deeply-related technologies.
And, you can see the relationship in very practical ways.
In LINQ, we take an input collection, and build a pipeline to get a new collection. A pipeline is
assembled in stages, and at each stage something is added, removed, or refined—until we end up with a
13. CHAPTER 1 ■ INTRODUCING LINQ AND RX
4
new collection. The truth is, however, that the new collection is really just a modified version of the
original collection.
At first glance, collections and streams are very different, but they are related in key ways, which is
why we refer to streams as future collections. Both streams and collections are sequences of items in a
particular order. LINQ and Rx let us transform these sequences into new sequences.
Throughout this book and when you are trying out the examples, always have this question in your
head: “How is the sequence that I want related to the sequence that I have?” In other words, the LINQ or
Rx pipeline that you build describes how the output is created from the input.
Why Bother?
After all is said and done, the practical programmer wants to know if the benefits of learning LINQ and
Rx are worth the investment of time and effort. What do these technologies bring to the party that you
can’t get elsewhere?
LINQ and Rx are:
• First-class members of .NET. This allows for full support from IntelliSense and
syntax highlighting in Visual Studio and LINQPad.
• Designed to work with all forms of data, from databases to XML to files.
• Highly extensible. It allows you to create your own libraries in order to extend
their functionality.
• Composable. As noted, both LINQ and Rx are used to combine and compose more
complex operations out of simpler building blocks.
• Declarative. LINQ and Rx bring a bit of functional programming to your code, as
described later in this chapter.
• Simplifying. Many constructs that take just a line or two of LINQ or Rx would
otherwise take many complex lines of confusing code.
We’ll take a closer look at Rx and LINQ throughout the rest of the book, but first, you need to get
acquainted with the tools you’ll use: Visual Studio and LINQPad.
Choosing your IDE
The examples in this book can be run in any version of Visual Studio 2010 or later, or, with very minor
modifications, in LINQPad, a free utility (download at http://linqpad.net) that provides an easy way to
quickly try out sets of LINQ statements without the overhead of a complete development environment.
Let’s look at a simple example, first using Visual Studio and then LINQPad. To begin, open Visual
Studio and create a new Console Application named QueryAgainstListOfIntegers. The complete source
code is as follows:
14. CHAPTER 1 ■ INTRODUCING LINQ AND RX
5
using System;
using System.Collections.Generic;
using System.Linq;
namespace QueryAgainstListOfIntegers
{
internal class Program
{
private static void Main( string[ ] args )
{
var list = new List<int>( ) { 1, 2, 3, 5, 7, 11, 13 };
var enumerable = from num in list
where num < 6
select num;
foreach ( var val in enumerable )
Console.WriteLine( val );
}
}
}
This small program illustrates a number of important things about LINQ syntax and features that
appeared for the first time in C# 3.0 and 4.0. Before we begin our analysis, however, let’s take a look at
how the tools can make it easy to create and run the code.
You can copy and paste the LINQ statements at the heart of the program from Visual Studio into
LINQPad to try them out in that utility. To do this, first set the language in LINQPad to “C# Statements”
and copy in the following code:
var list = new List<int>( ) { 1, 2, 3, 5, 7, 11, 13 };
var enumerable = from num in list
where num < 6
select num;
foreach ( var val in enumerable )
Console.WriteLine( val );
■ Note You do not need the using statements or the method structures; just the LINQ statements that you want
to run.
Run this code with Visual Studio and then with LINQPad. You’ll see that they both produce the
same result, as follows:
15. CHAPTER 1 ■ INTRODUCING LINQ AND RX
6
1
2
3
5
C# and .NET Fundamentals
Now that you have both tools working, let’s take this small program apart line by line and examine the
constructs it uses. In the next section, we’ll review some features of C# and .NET, whose mastery is
fundamental to Rx and LINQ.
Var
Let’s consider the program from the beginning. The first line could have been written as follows:
List<int> list = new List<int>( ) { 1, 2, 3, 5, 7, 11, 13 };
The results would have been identical. The advantages of the var keyword are that you avoid
redundancy and that it is a bit terser. Further, there are times when you, as the programmer, may not
know the type and letting the compiler infer the type (as is done with var) can be a tremendous time-
saver—why look up the type when the compiler can figure it out?
Make no mistake, however; variables and objects declared with var are type safe. If you were to
hover your cursor over the variable name list, you’d find that it is of type List<int>, as shown in
Figure 1-1.
Figure 1-1. Showing that list is type safe
You can assign list to another List<int>, but if you try to assign it to anything else (for example, a
string) the compiler will complain.
Collection Initialization
A second thing to note in this very first line of the program is the use of initialization for the list.
new List<int>( ) { 1, 2, 3, 5, 7, 11, 13 };
Initialization follows the new keyword and the parentheses and is surrounded by braces. The
initialization has the same effect as if you had it written as follows:
16. CHAPTER 1 ■ INTRODUCING LINQ AND RX
7
var list = new List<int>();
list.Add(1);
list.Add(2);
list.Add(3);
list.Add(5);
list.Add(7);
list.Add(11);
list.Add(13);
But again, initialization is easier to read and to maintain.
The following three lines constituted a LINQ query expression:
var enumerable = from num in list
where num < 6
select num;
This begins with the use of the keyword var, this time for the variable named enumerable. Once
again enumerable is type safe, in this case the variable enumerable’s true type is IEnumerable<int>.
The first line of the LINQ query is a from statement, in this case creating the temporary variable
number and indicating that we are selecting from a list of integers named list.
The second line is a where clause which narrows the answer space to those values that are less than
six. The final line is a select statement or projection of the results.
The net effect of these three lines of code is that enumerable is an IEnumerable of integers that
contains all of the values from the list whose value is less than six.
IEnumerable
Because enumerable is an IEnumerable, you can run a foreach loop over it, as done here.
As will be noted many times in this book, IEnumerable is the heart of LINQ, and as we will see,
IObservable is the heart of Reactive Extensions.
IEnumerable is an interface; classes that implement that interface will provide MoveNext(), Current()
and Reset().
Typically you can ignore this implementation detail, as you do when using for each, but
understanding how IEnumerable works is critical to seeing the connection between Link and Reactive
Extensions.
You can, in fact, rewrite the foreach loop using the following operators:
var e = enumerable.GetEnumerator( );
while ( e.MoveNext( ) )
{
Console.WriteLine( e.Current );
}
This will produce the same result, and in fact, the foreach construct should best be thought of as
shorthand.
Properties
Properties have the wonderful characteristic of appearing to be member-fields to the client of the class,
allowing the client to write, for example, the following:
17. CHAPTER 1 ■ INTRODUCING LINQ AND RX
8
int x = thePerson.Age; // Age is a property that appears to be a field
In this case, Age will appear to the author of the class as a method, allowing the owner of the class to
write the following:
public int Age
{
get { return birthdate – DateTime.Now; }
set { //... }
}
Automatic Properties
In a normal property, there is an explicit getter and setter; however, it is very common to find yourself
writing something like the following:
private int _age;
public int Age
{
get { return _age; }
set { _age = value; }
}
In this common idiom, you have a backing variable and the property does nothing but return or set
the backing variable. To save typing, C# now lets you use automatic properties.
public int Age { get; set; }
The IL code produced is identical—the automatic property is just shorthand for the idiom.
Note that automatic properties require both a setter and a getter (though one can be private) and
you cannot do any work in either. That is, if your getter or setter needs to do any work (for example, call
a method) then you must revert to using normal syntax for properties.
Object Initialization
Assume that you create a Person class that looks like the following:
public class Person
{
public string FullName { get; set; }
public string Address { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
}
18. CHAPTER 1 ■ INTRODUCING LINQ AND RX
9
The traditional way to instantiate an object and set its values is as follows:
var person = new Person();
person.FullName = "Jesse Liberty";
person.Address = "100 Main Street, Boston, MA";
person.Email = "jliberty@microsoft.com";
person.Phone = "617-555-1212";
Object initialization lets you initialize the properties in the Person instance, as follows:
var per = new Person()
{
FullName = "Jesse Liberty",
Address = "100 Main Street, Boston, MA",
Email = "jliberty@microsoft.com",
Phone = "617-555-1212"
};
Delegates
Much ink has been spilled attempting to explain delegates, but the concept is really fairly simple. A
delegate type is the definition of an object that will “hold” a method. At the time you create a delegate
type, you don’t necessarily know exactly which method it will hold, but you do need to know the return
type and parameters of that method.
A delegate instance is an instance of a delegate type. By creating an instance of the delegate, you
can use that instance to invoke the method it “holds.” For example, you might know that you are going
to invoke one or another method that determines which shape to draw, but you have a number of such
methods. Each of these methods returns a string (with the name of the shape) and each takes an integer
(used to decide which shape will be drawn). You would declare the delegate type as follows:
public delegate string Shaper (int x);
You would declare the instance as follows:
Shaper s = Shape;
Shape is the name of a method. The following is shorthand for:
Shaper s = new Shaper(Shape);
You would invoke the Shape method through the delegate with the following:
s(3);
This, again, is shorthand for the following:
s.Invoke(3);
The result would be identical to calling the following:
Shape(3);
The following is a complete program you can drop into LINQPad to see this at work:
19. CHAPTER 1 ■ INTRODUCING LINQ AND RX
10
public delegate string Shaper (int x);
void Main()
{
Shaper s = Shape;
string result = s( 3 );
Console.WriteLine( result );
}
static string Shape(int x)
{
if ( x % 2 == 0 )
return "Circle";
else
return "Square";
}
Anonymous Methods
Anonymous methods allow you to use an inline, unnamed delegate. Thus, the previous program could
be rewritten as follows:
public delegate string Shaper (int x);
void Main()
{
Shaper s = delegate(int x)
{
if ( x % 2 == 0 )
return "Circle";
else
return "square";
};
string result = s( 3 );
Console.WriteLine( result );
}
Lambda Expressions
Anonymous methods are made somewhat obsolete by lambda expressions. The previous could be
rewritten as follows:
public delegate string Shaper (int x);
void Main()
{
Shaper s = x =>
{
if ( x % 2 == 0 )
return "Circle";
else
return "Square";
20. CHAPTER 1 ■ INTRODUCING LINQ AND RX
11
};
string result = s( 3 );
Console.WriteLine( result );
}
Lambda expressions are just a shorthand notation for declaring delegates.
To see this at work, let’s begin by creating a very simple (if absurd) program that declares a delegate
to a method that takes two integers and returns a string, and then uses that delegate to call the method.
public MainPage( )
{
InitializeComponent( );
Func<int, int, string> AddDelegate = Add;
string result = AddDelegate( 5, 7 );
TheListBox.Items.Add( result );
}
private string Add( int a, int b )
{
return ( a + b ).ToString( );
}
Taking this apart, on line 4 we declare AddDelegate to be a delegate to a method that takes two
integers and returns a string, and we assign that delegate to the Add method declared at the bottom of
the listing. We then invoke the method through that delegate on line 5, and on line 6 we add the result to
the list box we created in MainPage.xaml.
Lambda syntax allows us to write the same thing much more tersely.
Func<int, int, string> AddLambda = ( a, b ) => ( ( a + b ).ToString( ) );
result = AddLambda( 3, 8 );
TheListBox.Items.Add( result );
The first line (broken over two lines to fit) declares AddLambda to be a lambda expression for a
method that takes two integers and returns a string, and then replaces the body of the method it points
to with what is written to the right of the equal sign. Immediately to the right of the equal sign, in
parentheses are the parameters. This is followed by the lambda symbol (=>) usually pronounced “goes
to” and that is followed by the work of the anonymous method.
Lay them out side by side and the left-hand side is identical, as follows:
Func<int, int, string> AddDelegate =
Func<int, int, string> AddLambda =
To the right of the equal sign, the method takes its parameters and the lambda takes its parameters,
as follows:
( int a, int b )
( a, b )
The lambda, however, uses type inference to infer the type of the parameters.
The body of the method and the body of the lambda expression are nearly identical.
21. CHAPTER 1 ■ INTRODUCING LINQ AND RX
12
private string Add( int a, int b )
{
return ( a + b ).ToString( );
}
=> ( ( a + b ).ToString( ) );
Once you have the mapping in your head, it all becomes a trivial exercise, and you quickly become
very fond of lambda expressions because they are easier to type, shorter, and easier to maintain.
Note that if you do not have any parameters for the lambda, you indicate that with empty
parentheses, as follows:
() => ( // some work );
Hello LINQ
Let’s take a look at another very simple LINQ program in which we extract the even numbers from a list
of numbers (which, admittedly, can be done even more easily without LINQ!), as shown in Listing 1-1.
Listing 1-1. Not Hello World
List<int> ints = new List<int>()
{ 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
var query = from i in ints
where i % 2 == 0
select i;
foreach ( var j in query )
Console.Write ("{0} ", j);
The output is as follows:
2 4 6 8 10 12 14
The structure of this program is basic to almost any LINQ program. We begin with an enumerable
collection (our List of integers). The heart of the program is the LINQ query, which begins (always) with
a from statement and ends (always) with a select (projection) statement, as shown here or with group
by. In the middle are filters (the where statement) or one or more of the LINQ operators that we’ll be
discussing throughout the book.
The result of the query is stored in the local variable query and is itself an IEnumerable, which is why
you can iterate over it in the foreach loop.
We’ll say this repeatedly throughout the book: in LINQ and Rx you begin with a collection and you
end with a collection.
22. CHAPTER 1 ■ INTRODUCING LINQ AND RX
13
Hello Rx
The problem with creating your first Rx program is that if it is simple enough to qualify for the job, then
it is too simple to do anything more useful than what could be done without it. The following is an
example:
var input = Observable.Range(1,15);
input.Subscribe(x => Console.WriteLine("The number is {0}", x));
As you’ll learn in Chapter 3, the Range operator takes two parameters: a starting value and the
number of values to generate. In this case, it will generate the numbers 1 through 15. The return of
Observable.Range is of type IObservable of the inferred type int. If you hover over the input in the first
line —in LINQPad or Visual Studio, but not, unfortunately, in this book—you will see that it is of the
following type:
System.IObservable<int>
As an IObservable you can subscribe to it, which we do on the second line. The lambda statement
can be read as “for each value in the IObservable I subscribe to, run the statement
Console.WriteLine(...).” The output is as follows:
The number is 1
The number is 2
The number is 3
The number is 4
The number is 5
The number is 6
The number is 7
The number is 8
The number is 9
The number is 10
The number is 11
The number is 12
The number is 13
The number is 14
The number is 15
You’ll read much more about observable collections and how to subscribe to them in this and future
chapters.
Collections
Both LINQ and Rx work with collections. LINQ works with collections that implement IEnumerable,
which we will call enumerable collections. Rx works with collections that implement an extension to
IEnumerable, IQueryable, which will refer to as observable collections.
The enumerable collection is familiar, as it is the basis for foreach loops in C#. Each enumerable
collection is able to provide the Next item in the collection until the collection is exhausted. The details
of how this is done will be covered later in the book.
23. CHAPTER 1 ■ INTRODUCING LINQ AND RX
14
Enumerable Collections
Every generic collection provides the ability to enumerate the values. This means that each collection
allows you to ask for the first element, then the next, and the following and each element in turn until
you stop asking or the collection runs out of elements.
The underlying mechanism is that these collections implement IEnumerable. The IEnumerable
interface is as follows:
Public interface IEnumerable<T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
}
The GetEnumerator method returns an IEnumerator. The IEnumerator interface is as follows:
Public interface IEnumerator
{
Object Current { get; }
Bool MoveNext();
Void Reset();
}
You can rewrite Listing 1-1 with an explicit enumerator, as follows:
List<int> ints = new List<int>()
{ 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
var query = from i in ints
where i % 2 == 0
select i;
IEnumerator<int> e = ints.GetEnumerator();
while (e.MoveNext())
{
Console.WriteLine(e.Current);
}
In fact, the foreach loop is just a shorthand way of writing these examples.
Observable Collections
Rx works with an observable collection, itself based on the Observer design pattern (see the upcoming
sidebar).
As noted, a Reactive Extensions observable collection is not the same thing as a Silverlight
ObservableCollection control, though both are based on the observer design pattern.
■ Note Unless specifically stated otherwise, for the remainder of this book, if we use the term “observable
collection,” we mean the Rx collection type, not the Silverlight control.
24. CHAPTER 1 ■ INTRODUCING LINQ AND RX
15
THE OBSERVER DESIGN PATTERN
The Observer design pattern was first codified in Design Patterns: Elements of Reusable Object-Oriented
Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides (Addison-Wesley, 1994). The
four authors are affectionately known as the “Gang of Four” and the book is often referred to as the “GOF
(pronounced goff ) Book.”
The essence of the Observer pattern is that there is an observable object (such as a clock), and there are
observers who wish to be informed when the observable changes (e.g., when a second passes).
An ObservableCollection in Silverlight implements this pattern in that it automatically raises
PropertyChanged notifications for any object in the collection that is altered.
The observable collection used by Rx, on the other hand, is a more general implementation of the Observer
pattern, allowing for other objects to subscribe to any change whatsoever in the collection (including the
arrival of new entries).
The term subscribe comes from the closely related Publish and Subscribe design pattern. In fact, the
Observer pattern is a subset of Publish/Subscribe. The observable (clock) can be said to publish certain
events (e.g., a clock tick) and the observers can be said to subscribe to those events. This is very much
the pattern with event handling in .NET, in which interested objects might subscribe to a button’s click
event.
For more coverage of the Publish/Subscribe and its patterns, please see the Wikipedia articles at
http://en.wikipedia.org/wiki/Publish/subscribe and at
http://en.wikipedia.org/wiki/Observer_pattern, respectively.
Observable Collections vs. Enumerable Collections
Enumerable collections have all their members present at the moment that you are ready to work with
them. You can “pull” each item from the collection when you are ready for it, adding, for example, each
in turn to a list box.
An observable collection, however, does not have its members available when first created; the
members will be added to the collection over time (perhaps by acquiring them from a web service). In
this sense, an observable collection is a “future collection.” As the members of the observable collection
are added, they are pushed out to the subscribers who have registered with the collection, who may then
work with these members as they each become available.
In the next section we’ll work through an example that will clarify this discussion.
Example: Working with Enumerable and Observable Collections
In this example, we’re going to create a pair of ListBoxes for a Windows Phone. We’ll populate one from
an enumerable collection and the other from an observable collection.
25. CHAPTER 1 ■ INTRODUCING LINQ AND RX
16
The following tells how:
1. Fire up Visual Studio, create a new Windows Phone project, and on the first
page divide the Content panel into two columns. Place a ListBox in each of
the two columns, naming the first list box, lbEnumerable, and the second,
lbObservable.
2. Add a reference to each of the following libraries:
Microsoft.Phone.Reactive
System.Observable
3. Add a using statement, as follows:
using Microsoft.Phone.Reactive
4. Next, we need some data so let’s initialize a List<string> with the names of
the first five American presidents, as follows:
public partial class MainPage :
PhoneApplicationPage
{
readonly List<string> names =
new List<string>
{
"George Washington",
"John Adams",
"Thomas Jefferson",
"James Madison",
"James Monroe",
"John Quincy Adams"
};
■ Note Read List<string> as “list of string.”
This list will be the source for populating both list boxes. We’ll do all of the work in a method called
PopulateCollection(), which we’ll call from the page’s constructor.
5. In the first part of PopulateCollection(), we’ll take the traditional approach
and iterate through each member of the collection, adding each president’s
name to the list box. To do that, add the following code in MainPage.xaml.cs.:
private void PopulateCollection( )
{
foreach ( string pName in names )
{
lbEnumerable.Items.Add( pName );
}
26. CHAPTER 1 ■ INTRODUCING LINQ AND RX
17
6. To handle the fact that these names might not be immediately available, use
the ToObservable() extension method on the list of names, to create an
instance of an IObservable collection, as follows:
IObservable<string> Observable =
names.ToObservable( );
■ Note Extension methods are new in C# 4. They allow you to create a static method that appears to extend an
existing class, though they are not actually inserted into the class. In this case, the ToObservable appears to
extend the generic List class, and can be used just as if it were a method of List<T>.
7. ObservableCollection has an overloaded Subscribe method. The first
parameter is of type Action<T>, which subscribes a value handler to an
Observable sequence. Add the following code to MainPage.xaml.cs, as well:
Observable.Subscribe<string>
(
pName =>
{
lbObservable.Items.Add( pName );
}
);
Listing 1-2 illustrates the body of the Action<T>, which in this case is a lambda expression indicating
that, given a string, pName, the body of the method adds that name to the appropriate list box. Listing 1-2
is the complete listing.
Listing 1-2. Observable Collection
using System;
using System.Collections.Generic;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Reactive;
namespace Observable_Collection
{
public partial class MainPage :
PhoneApplicationPage
{
readonly List<string> names =
new List<string>
{
"George Washington",
"John Adams",
"Thomas Jefferson",
27. CHAPTER 1 ■ INTRODUCING LINQ AND RX
18
"James Madison",
"James Monroe",
"John Quincy Adams"
};
public MainPage( )
{
InitializeComponent( );
PopulateCollection( );
}
private void PopulateCollection( )
{
foreach ( string pName in names )
{
lbEnumerable.Items.Add( pName );
}
IObservable<string> Observable =
names.ToObservable( );
Observable.Subscribe<string>
(
pName =>
{
lbObservable.Items.Add( pName );
}
);
}
}
}
Run the program. As you can see, the two lists are identical, even though the second list, handled by
Rx, has members that might have been obtained from a web service and thus would not have been
available when the program began.
Summary
In this chapter you learned the purpose of LINQ and Rx and saw how Rx extends LINQ. You also saw
how IEnumerable is the key interface for LINQ and IObservable is the key interface for Rx. In coming
chapters, you’ll learn the important operators for both LINQ and Rx, and how to use these technologies
to master some of the more important and complex operations in C# programming, such as managing
data and handling asynchronicity.
28. C H A P T E R 2
19
Core LINQ
The relationship between LINQ and Reactive Extensions (Rx) is deep and fascinating. We will explore the
relationship between the two in Chapter 5, but for now it is sufficient to point out that the central
element in LINQ is IEnumerable, much as the central element in Rx is IObservable. Many of the same
operators appear in both frameworks, such as Select and SelectMany.
In this chapter we will dive into LINQ in a bit more depth, first exploring the syntax of a LINQ
statement, and then a number of useful LINQ operators, and concluding with a demonstration program
that will illustrate many of the central concepts of LINQ.
LINQ Syntax
There are a number of flavors of LINQ, each tailored to work with different types of data. LINQ to Objects
is designed to work with objects in memory, while LINQ to SQL is designed to work with SQL server-
based data. The essence is the same, however, and LINQ to Objects shows that essence particularly well.
Consider the following statements, which you can copy and paste directly into LINQPad:
var primes = new List<int>() { 1, 2, 3, 5, 7, 11, 13, 17, 19 };
var query = from num in primes
where num < 7
select num;
foreach ( var i in query )
{
Console.WriteLine(i);
}
The central three lines of code compose a LINQ query expression. The body of the query expression
begins, as do all query expressions, with the keyword from. By placing the from clause first, LINQ is able
to provide type checking and IntelliSense support.
29. CHAPTER 2 ■ CORE LINQ
20
■ Note There is a second syntax used with LINQ, known as method syntax, that does not necessarily start with
the from clause. For example, you might rewrite this query as follows:
var query = primes
.Where (num => num < 7)
.Select (num => num);
The second line in the Query Expression is called the filter, and the final line is called the projection.
The job of the filter is to reduce the answer set, and the job of the projection is to extract the new
IEnumerable collection from the old.
Let’s take a closer look at three additional characteristics of a LINQ statement: its return of
IEnumerable types, deferred execution, and its powerful collection of operators.
IEnumerable
We are able to iterate over the values in query in the previous example (the foreach loop) because the
LINQ expression returns an IEnumerable. In fact, if you hover over the identifier query in either Visual
Studio or LINQPad you will see that it is identified to be of type IEnumerable, as shown in
Figure 2-1.
Figure 2-1. IntelliSense showing the type of query
■ Note The free version of LINQPad does not offer IntelliSense.
Query Operators
We’re going to look at several LINQ operators later in this chapter, but to get us started, examine Listing
2-1, in which we use the orderby and group by query operators to find all the methods of the int type.
Note that LINQ automatically uses reflection as needed, to obtain the methods we’re searching for.
30. CHAPTER 2 ■ CORE LINQ
21
Listing 2-1. The Code to Copy into LINQPad
var query = from method in typeof(int).GetMethods()
orderby method.Name
group method by method.Name into groups
select new
{ MethodName = groups.Key, MethodOverloads = groups.Count() };
foreach ( var item in query)
{
Console.WriteLine(item);
}
The query finds all the methods for int, orders them by name (and the output is alphabetical by
method name), and then groups the overloaded methods together (all the methods with the same
name), reporting on how many overloads each method has. Figure 2-2 shows the output as rendered by
LINQPad.
{ MethodName = CompareTo, MethodOverloads = 2 }
MethodName CompareTo
MethodOverloads 2
{ MethodName = Equals, MethodOverloads = 2 }
MethodName Equals
MethodOverloads 2
{ MethodName = GetHashCode, MethodOverloads = 1 }
MethodName GetHashCode
MethodOverloads 1
{ MethodName = GetType, MethodOverloads = 1 }
MethodName GetType
MethodOverloads 1
{ MethodName = GetTypeCode, MethodOverloads = 1 }
MethodName GetTypeCode
MethodOverloads 1
31. CHAPTER 2 ■ CORE LINQ
22
{ MethodName = Parse, MethodOverloads = 4 }
MethodName Parse
MethodOverloads 4
{ MethodName = ToString, MethodOverloads = 4 }
MethodName ToString
MethodOverloads 4
{ MethodName = TryParse, MethodOverloads = 2 }
MethodName TryParse
MethodOverloads 2
Figure 2-2. Output from LINQPad
Deferred Execution
A key aspect of LINQ is that execution of a query statement is deferred until you actually ask for the first
item in the sequence. Consider the following code:
var list = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var query = from num in list
where num < 7
select num;
foreach ( var num in query )
{
Console.WriteLine(num);
}
You might at first expect that the code would be evaluated when the LINQ statement is reached.
As you can see by running this in a debugger, however, the LINQ statement is not evaluated until the
foreach is run. This feature can have both good and bad consequences for the code that you write.
The Good
The following is an example that demonstrates deferred execution by slowing down the requests for
evaluation:
32. CHAPTER 2 ■ CORE LINQ
23
var q = Enumerable.Range(0, 1000 * 1000)
.Select(x =>
{
Thread.Sleep(1000);
return x * 10;
});
foreach (var num in q )
{
Console.WriteLine(num);
}
The query itself evaluates 1,000,000 numbers. For each number, it sleeps one second, and then
returns the number multiplied by 10. If we waited for it to evaluate all one million numbers before
running the foreach loop, we’d have to wait 11.57 days. Fortunately, because of delayed execution, we
get a result every second, as you can see by running this code in LINQPad or Visual Studio (note, again,
that if you run this in Visual Studio you will need to wrap it in a program structure).
The Bad
Deferred execution can have a downside, as well. In the next example, we set up a LINQ query and then
iterate through the results twice. The output may not be quite what we anticipated or want.
int counter = 0;
var evenNumbersInSeries = Enumerable.Range(0, 10).Select(x =>
{
int result = x + counter;
counter++;
return result;
});
Console.WriteLine("First Try:n");
foreach(int i in evenNumbersInSeries)
{
Console.WriteLine(i);
}
Console.WriteLine("nSecond Try:n");
foreach(int i in evenNumbersInSeries)
{
Console.WriteLine(i);
}
33. CHAPTER 2 ■ CORE LINQ
24
The output for these two runs is shown in the following two columns:
0 10
2 12
4 14
6 16
8 18
10 20
12 22
14 24
16 26
18 28
Notice that the second column begins where the first left off. That was not our intention; we
expected the two columns to be identical.
We could solve this problem in two ways. One is to reset the counter before running the second
loop. Another way to solve this is to freeze the evaluation by asking LINQ to convert the results (currently
in the variable evenNumberInSeries) to an array. The result would be that the entire series would be
evaluated and placed in the array and then we’d iterate through the array, as follows:
int counter = 0;
var evenNumbersInSeries = Enumerable.Range(0, 10).Select(x =>
{
int result = x + counter;
counter++;
return result;
}).ToArray();
// List the numbers in the series
Console.WriteLine("First Try:n");
foreach(int i in evenNumbersInSeries) {
Console.WriteLine(i);
}
// This time, because we added the ToArray(), we'll get the expected result
// every time.
Console.WriteLine("nSecond Try:n");
foreach(int i in evenNumbersInSeries) {
Console.WriteLine(i);
}
34. CHAPTER 2 ■ CORE LINQ
25
This code is identical to the earlier version except that we append .ToArray to the LINQ statement.
This time when we run the program, both columns are identical. Calling ToArray on the collection
causes it to be evaluated and “frozen” in the array, and thus when we iterate over the collection we are
iterating over the same array each time.
0 0
2 2
4 4
6 6
8 8
10 10
12 12
14 14
16 16
18 18
We’ve been using a number of LINQ operators; let’s take a closer look at the most important ones.
Core Operators
A key aspect of becoming comfortable with LINQ is taking advantage of its myriad of available operators.
We won’t try to provide an exhaustive list, but rather we’ll focus on some of the most useful among
them.
Any
The operator Any returns a Boolean value and can be used to determine if a sequence is empty or
whether it contains a particular predicate.
var firstList = Enumerable.Empty<int>();
var secondList = Enumerable.Range(1,10);
Console.WriteLine(
"The first list has members? {0}, The second list has members? {1}",
firstList.Any(), secondList.Any() );
This code returns the following:
The first list has members? False, The second list has members? True
Let’s add a second test, as follows:
Console.WriteLine(
"Is 6 in the second list? {0}, Is 12 in the second list? {1}",
secondList.Any(x => x==6),
secondList.Any(x => x==12) );
This second test returns the following:
Is 6 in the second list? True, Is 12 in the second list? False
35. CHAPTER 2 ■ CORE LINQ
26
■ Note Any returns as soon as it finds a match. If we had included 6 in the list three times, Any would return as
soon as it encountered the first.
Contains
A second way to determine if a value is in a list is to use the contains operator. This overloaded operator
checks to see if a value is in a list. We can rewrite the second test as follows:
var firstList = Enumerable.Empty<int>();
var secondList = Enumerable.Range(1,10);
Console.WriteLine("SecondList contains 6? {0}, Second List contains 12? {1}",
secondList.Contains(6), secondList.Contains(12));
The results are the same:
SecondList contains 6? True, Second List contains 12? False
As noted, though the syntax is simpler, the Contains operator is overloaded, and you are free to pass
in your own comparison method to facilitate comparing items of different types.
To see this at work, create a Console Application in Visual Studio. Within the application, create a
Person class and derived from that, a Student class, as follows:
class Person
{
public int ID { get; set; }
public string FullName { get; set; }
}
class Student : Person
{
public string Major { get; set; }
}
You now create a class derived from IEqualityComparer, which will establish whether two People
are equal or not using whatever you choose as the criteria. You might match FullName fields or, more
likely, ID:
class StudentToPersonEquals : IEqualityComparer<Person>
{
public bool Equals( Person x, Person y )
{
if (x.ID == y.ID)
return true;
else
return false;
}
36. CHAPTER 2 ■ CORE LINQ
27
public int GetHashCode( Person obj )
{
return obj.GetHashCode();
}
}
With this in place, you can now instantiate a number of Person and Student objects, and place them
in a collection, as follows:
static void Main( string[ ] args )
{
var people = new List<Person>();
var PaulBetts = new Student()
{ FullName = "Paul Betts", ID = 1, Major = "Computer Science" };
people.Add(PaulBetts);
people.Add( new Person() { ID = 2, FullName = "Jesse Liberty" } );
people.Add( new Student() {
ID = 3,
FullName = "George Washington",
Major = "Favorite on Fawlty Towers" } );
people.Add( new Student() {
ID = 4,
FullName = "John Adams",
Major = "History" } );
You can now use an instance of your StudentToPersonEquals class as the comparer for the Contains
operator, as follows:
Console.WriteLine ("People contains Paul Betts? {0}",
people.Contains(PaulBetts, new StudentToPersonEquals() ));
This allows you to pass in an instance of the Student class and have it identified by the Contains
operator based on the criteria specified in your comparison class.
Take
Take allows you to determine how many objects you’d like to retrieve from what may be a very large
collection, as follows:
var input = new[] {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20};
var output = input.Take(5).Select(x => x * 10);
output.Dump();
37. CHAPTER 2 ■ CORE LINQ
28
In this case, our source has 20 values. Without the Take operator, this code would return the values
10 through 200, but as it is, it returns only the first five of those values, as shown in Figure 2-3.
10
20
30
40
50
Figure 2-3. The Take operator
Distinct
Often you will have a collection with duplicate values. The Distinct operator removes the duplicates and
works only on the unique values, as follows:
var input = new[] {1,2,3,2,1,2,3,2,1,2,3,2,1};
var output = input.Distinct().Select(x => x * 10);
output.Dump();
The following result is the product of just the distinct values:
10
20
30
Zip
Zip is a fascinating operator. It is used to thread two lists together. The easiest way to do it is to call Zip
on the first list, passing in the name of the second list, and then a lambda statement indicating how you
want the lists zipped together. The result of Zip is an Enumerable.
string[] codes = { "AL", "AK", "AZ", "AR", "CA" };
string[] states = {"Alabama", "Alaska", "Arizona", "Arkansas", "California" };
var CodesWithStates = codes.Zip(states, (code, state) => code + ": " + state);
foreach ( var item in CodesWithStates )
{
Console.WriteLine(item);
}
In this case, CodesWithStates is an Enumerable produced by zipping the two lists, codes and states,
together. The output is as follows:
38. CHAPTER 2 ■ CORE LINQ
29
AL: Alabama
AK: Alaska
AZ: Arizona
AR: Arkansas
CA: California
For the Zip operator to work, the two lists do not have to be of the same type, nor of the same length.
The following is a quick second example:
int[] codes = Enumerable.Range(1,100).ToArray();
string[] states = {"Alabama", "Alaska", "Arizona", "Arkansas", "California" };
var CodesWithStates = codes.Zip(states, (code, state) => code + ": " + state);
foreach ( var item in CodesWithStates )
{
Console.WriteLine(item);
}
In this second case, the codes array is an array of 100 integers. Zip puts the two disparate lists
together until it runs out of one list (in this case, states), in which case it terminates. The output is as
follows:
1: Alabama
2: Alaska
3: Arizona
4: Arkansas
5: California
SelectMany
One of the most powerful operators in LINQ is also one of the most difficult to understand. The
principal use of SelectMany is to flatten collections of collections (or hierarchies) into single dimension
collections, but as we’ll see in a later section, it has other uses as well. Thus, if we have [1,2,3],[4],[5,6]
and we run SelectMany, the result will be a single collection: [1,2,3,4,5,6].
It is critical to come to a complete understanding of SelectMany because the Reactive version is
instrumental in helping us chain calls to asynchronous methods. In the following two sections, we’ll take
a look, first, at its use to flatten hierarchies, and then to recursively traverse them.
Flattening Hierarchies
To better understand the use of the SelectMany operator to flatten hierarchies, let’s look at two examples.
In our first example, we will create an object, Book, which will contain a list of authors.
class Book
{
public string Title { get; set; }
public List<Author> Authors { get; set; }
}
39. CHAPTER 2 ■ CORE LINQ
30
class Author
{
public string FullName { get; set; }
}
We then create a method to get books and their authors. You might do this by calling into a
database or a web service, but we can mock it up using a simple static method that will create the books
and return a collection of, in this case, two books, each populated with one or more authors.
private static List<Book> GetBooks()
{
var books = new List<Book>()
{
new Book
{
Title = "Programming C#",
Authors = new List<Author>()
{
new Author { FullName = "Jesse Liberty" },
new Author { FullName = "Ian Griffiths" },
new Author { FullName = "Matthew Adams" }
}
},
new Book
{
Title = "Programming Reactive Extensions and LINQ",
Authors = new List<Author>()
{
new Author { FullName = "Jesse Liberty" },
new Author { FullName = "Paul Betts" }
}
}
};
return books;
}
We’re now ready to use LINQ to get the authors of both books, as follows:
var books = GetBooks();
var q = from b in books
select b.Authors;
The first line obtains the collection and the next two lines are the LINQ query to obtain the authors
of all the books in the collection.
How might we display these names? We could do this with a for loop, but q does not contain a
collection of authors, it contains a collection of books, each of which contains a collection of authors. To
get to the authors, we need nested foreach loops, as follows:
40. CHAPTER 2 ■ CORE LINQ
31
foreach (var book in q)
{
foreach (var auth in book)
{
Console.WriteLine( auth.FullName );
}
}
The output from these loops is the names of all the authors of both books in the collection, as
follows:
Jesse Liberty
Ian Griffiths
Matthew Adams
Jesse Liberty
Paul Betts
You can accomplish the same thing as the nested foreach loops by flattening the hierarchical
structure using SelectMany.
var books = GetBooks();
var q = books.SelectMany( book => book.Authors );
foreach (var author in q)
{
Console.WriteLine( author.FullName );
}
SelectMany takes the books collection and flattens it, allowing you to obtain all the Authors as if they
were in a single, non-hierarchical list.
To replicate this example, create a Console application using the complete source code shown in
Listing 2-2.
Listing 2-2. Using SelectMany
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace SelectMany
{
class Program
{
static void Main( string[ ] args )
{
var books = GetBooks();
// alternative 1
//var q = from b in books
// select b.Authors;
//foreach (var book in q)
//{
41. CHAPTER 2 ■ CORE LINQ
32
// foreach (var auth in book)
// {
// Console.WriteLine( auth.FullName );
// }
//}
// alternative 2
var q = books.SelectMany( book => book.Authors );
foreach (var author in q)
{
Console.WriteLine( author.FullName );
}
}
private static List<Book> GetBooks()
{
var books = new List<Book>()
{
new Book
{
Title = "Programming C#",
Authors = new List<Author>()
{
new Author { FullName = "Jesse Liberty" },
new Author { FullName = "Ian Griffiths" },
new Author { FullName = "Matthew Adams" }
}
},
new Book
{
Title = "Programming Reactive Extensions and LINQ",
Authors = new List<Author>()
{
new Author { FullName = "Jesse Liberty" },
new Author { FullName = "Paul Betts" }
}
}
};
return books;
}
}
class Book
{
public string Title { get; set; }
public List<Author> Authors { get; set; }
}
42. CHAPTER 2 ■ CORE LINQ
33
class Author
{
public string FullName { get; set; }
}
}
■ Caution It is imperative that you are comfortable with what SelectMany is doing in this example before you go
forward in this book. We strongly recommend taking the complete code and putting it into Visual Studio and
playing with it in the debugger. Try replacing SelectMany with Select and see what happens (hint, you’ll need a
nested foreach loop again).
Our second example will take advantage of SelectMany’s flattening to allow for a very clean use of
recursion.
Recursively Traversing Hierarchies
The explanation of SelectMany as flattening a hierarchy is correct, and fine as far as it goes, but it is a
slight oversimplification. A more complete way to think about SelectMany is, “for each item in this list, I
can replace it with nothing, a single item, or another list.”
In the example of flattening, we replace each item in each of the three lists with the same item in the
output list. But we can also replace each item with two items, or with a function or with just about
anything we like.
In the next LINQPad example, we’ll use SelectMany and flattening to manage the hierarchy of folders
in a directory, allowing us to recurse over each subdirectory in turn.
IEnumerable<string> GetFilesInAllSubdirectories(string root)
{
var di = new System.IO.DirectoryInfo(root);
return di.GetDirectories()
.SelectMany(x => GetFilesInAllSubdirectories(x.FullName))
.Concat(di.GetFiles().Select(x => x.FullName));
}
void Main()
{
var allFilesOnDesktop = GetFilesInAllSubdirectories(
System.Environment.GetFolderPath(Environment.SpecialFolder.Desktop));
allFilesOnDesktop.Dump();
}
The key line in this program is the second line in the return statement in the
GetFilesInAllSubdirectories method, as follows:
.SelectMany(x => GetFilesInAllSubdirectories(x.FullName))
43. CHAPTER 2 ■ CORE LINQ
34
It is critical to understand what this is doing, and how SelectMany makes this entire method work.
Let’s take this apart line by line. We begin in Main, where we call into the recursive method, passing
in the folder path to the Desktop. We will not return until all the recursive calls are completed.
In the recursive method (GetFilesInAllSubdirectories) we begin by getting a DirectoryInfo on
whatever parameter was passed in (initially Desktop). We then call GetDirectories, which gives us a
collection of all the sub-directories for that directory. SelectMany then calls recursively into the same
method, passing in the name of each of these subdirectories.
Once we reach the lowest level of subdirectory, we use the Concat operator to add each file name
from the call to GetFiles(), which we append with Select to obtain the full name of the file.
What is critical here is that the GetDirectories method returns a hierarchy of directories, and
SelectMany flattens that into a collection of Directories so that we can recurse back into the method
with each in turn.
Understanding the LINQ operators is fine and important, but to truly internalize their use, we need
a meaningful example.
Example: Parsing a Tab Separated File
A real-world challenge, made more tractable by LINQ, is parsing a delimited file and extracting useful
information as if it came from a normalized database. In this next example, we parse the contents of a
tab-separated, value text file. The following are the first few lines of the file and its tab-separated fields:
name id beer_style first_brewed alcohol_content original_gravity
final_gravity ibu_scale country brewery_brand color_srm from_region
containers
Miller Genuine Draft /m/02hv39w
Hakim Stout /m/059q7h1 Stout 5.8
Harar Beer Factory Ethiopia
Wellington County Dark Ale /m/04dqvym 5.0
Wellington Canada /m/04dr5xv
De Regenboog 't Smisje Honingbier /m/04dqbhm 6.0
Regenboog Brewery Belgium /m/04dqxph
Hop Back Entire Stout /m/04dqf8w 4.5
Hop Back Brewery United Kingdom /m/04dqzb7
As you can see, the first line has the headings, each separated by a tab. The following lines have the
data, though not every line is complete. Incomplete lines have multiple tabs so that, if this page were
wide enough, each column would align properly.
Let’s see what we can do with this data using a variety of LINQ statements.
We begin by opening the file. Our input data is the raw file as an array of strings, one on each line.
We use LINQ to parse the raw data and to calculate statistics.
var lines = File.ReadAllLines(@"beer.tsv");
First, we want to read the column names from the first line of the file. In a non-LINQ world, we
might write code that looks like this, to generate a Dictionary whose keys are the column names, and
whose values are the index of the column (i.e. "from_region => 6").
44. CHAPTER 2 ■ CORE LINQ
35
int i = 0;
var headers = new Dictionary<string, int>();
foreach(var header in lines[0].Split('t')) {
headers[header] = i;
i++;
}
headers.Dump("Imperatively make a dictionary of headers");
The output for this non-LINQ version is shown in Figure 2-4.
Key Value
ne
id 1
beer_style 2
first_brewe 3
alcohol_content 4
original_gravity 5
final_gravity 6
ibu_scale 7
country 8
brewery_brand 9
olor_srm 10
from_region 11
containers 12
78
Figure 2-4. Dictionary of headers, imperative
Now let’s try that again using LINQ to implement a functional approach to a solution. We begin by
zipping together the list with the numbers 0..100 (as shown in the following example).
45. CHAPTER 2 ■ CORE LINQ
36
headers = Enumerable.Zip(
lines.First().Split('t'),
Enumerable.Range(0,100),
(header, index) => new {header, index})
.ToDictionary(k => k.header, v => v.index);
headers.Dump("The functional version returns the same result");
The output is identical, as shown in Figure 2-5. The functional version returns the same result.
Key Value
name 0
id 1
beer_style 2
first_brewed 3
alcohol_content 4
original_gravity 5
final_gravity 6
ibu_scal 7
country 8
brewery_brand 9
color_srm 10
from_region 11
containers 12
78
Figure 2-5. Dictionary of headers, functional
Note that this time, instead of calling Zip on one list and passing in the second, we call Zip on
Enumerable and pass in both lists. The effect is the same.
47. aged naturalist, who had withdrawn from civilization and the life of
white people to settle in one of the remotest recesses of the
Amazonian jungle. Whether he was related to him, Bomba did not
know and had never wondered. He would not have known the
meaning of relative. He only knew that he loved Casson and that
Casson loved him, although the latter seldom demonstrated any
affection in words, spending days at a time in moody abstraction.
The old man’s state had grown worse after a certain memorable day
when he had fired a gun at an anaconda which was threatening to
attack Bomba and the weapon had burst in his hands. The reptile
was wounded by the flying missiles and retreated, but Casson had
received a serious injury to his head. Bomba nursed him back to
some degree of physical health, but Casson from that time on was
half-demented, and the care of providing for the two had fallen on
the lad’s shoulders.
For such youthful shoulders it was a heavy burden, but it helped to
develop the lad into a wonder of strength and daring. Dangers of all
kinds surrounded him, wild beasts and reptiles with which the jungle
swarmed, and only quick wit and dauntless courage could preserve
his life. But necessity is a hard taskmaster, and under its spur
Bomba learned all the craft of the jungle. Keen of eye, swift of foot,
supple of muscle, and strong of heart, he matched himself against
his foes and so far had come out the victor. He was now about
fourteen years old, but few grown men had his strength and
resources.
Of the outside world he knew nothing. All his life was circumscribed
by the jungle. Casson had started to give him a smattering of
learning, but the explosion of the rifle had brought this to an abrupt
stop.
So Bomba roamed the jungle like a young faun at the beginning of
the world. His face was as bronzed as that of an Indian from
constant exposure to sun and storm. But there was undeniable proof
in his features, in his aquiline nose, his firm jaw, his brown hair and
eyes, that he was of white blood. He wore the native tunic, or
mendiyeh, and a puma skin was slung across his breast—that of
48. Geluk the puma that he had come across and killed when it was
trying to slay the friendly parrots, Kiki and Woowoo. Beneath his
bare arms and legs powerful muscles glided and rippled. Homemade
sandals encased his feet.
His weapons consisted of a bow and arrows, and he wore at his belt
a machete, or two-edged knife, fully a foot in length, a fearful blade
when it came to hand-to-hand fighting. In addition he had a five-
chambered revolver, the only firearm of which he was possessed,
and which had been given to him by two white rubber hunters after
he had rendered them a signal service.
Despite its perils, he loved the life of the jungle, and but for one thing
would have been reasonably happy. That thing was the
consciousness of his white blood. It tugged at his heart, and while it
gave him pride, it also tormented him. The call of the blood was
strong within him. He knew that, somehow, he was out of place.
Something was always calling him to go elsewhere, beckoning him
on to new horizons, telling him that he belonged to the white people.
He had a great yearning to know of his parentage. He had not the
slightest memory of his father or mother. Again and again he had
questioned Casson on this point, but the old man’s memory always
failed him at the very moment of revelation. In these efforts to recall
the past Casson had frequently muttered the words “Bartow” and
“Laura,” and Bomba had inferred that the names were those of his
father and mother. But the further knowledge he craved was denied
him.
How Bomba saved the camp of Gillis and Dorn, rubber hunters, from
a night attack by jaguars—how he trapped the deadly cooanaradi,
the most dreaded serpent of the South American wilds, when it
pursued him; his adventures with alligators and anacondas; the
besieging of his cabin by the headhunters; how his friends of the
forest came to his aid when he was fearfully beset; all this is narrated
in the first volumes of this series, entitled: “Bomba, the Jungle Boy;
or, The Old Naturalist’s Secret.”
Later on, Casson told Bomba that, though he himself could not
remember the facts about the lad’s parentage, the latter could get
49. that information from Jojasta, the medicine man of the Moving
Mountain. Bomba, therefore, after providing for Casson’s safety
while he should be gone, set out to see Jojasta. From the very outset
his path was beset with perils. Flood and earthquake, man and beast
sought his life. He was instrumental in delivering from the hands of
the savages a Mrs. Parkhurst and her son, Frank, and his
association with the two deepened his desire to know more of that
white civilization with which they were so familiar. He was hurled into
a subterranean cavern, escaped by a hair’s breadth and finally
reached Jojasta and the Moving Mountain.
There disappointment awaited him; but he was told that if he could
find Sobrinini, the witch who dwelt near the Giant Cataract, she
might give him the knowledge for which his soul longed.
Baffled for a time but not disheartened, Bomba resolved to search
out Sobrinini, though warned that great peril would attend the
attempt. How true that warning was he soon had reason to learn. He
fell into the power of Nascanora and was doomed by him to torture
and death. How his quick wit saved him; the terrible dangers to
which his indomitable spirit refused to yield, and which he finally
surmounted; how he found Sobrinini at last on her island of snakes
and brought her back with him, only to be tantalized with imperfect
revelations that made it still necessary to hunt out Japazy on Jaguar
Island is told in the preceding volume of this series, entitled, “Bomba,
the Jungle Boy, at the Giant Cataract; or, Chief Nascanora and His
Captives.”
And now to return to Bomba as he writhed and struggled to shake
himself free from that terrible grip on his throat!
He knew that he was fighting for his life.
What was it that had waited for him with the stealth of the panther to
leap upon him as he passed?
That one of the headhunters of the tribe of Nascanora had him in his
grip, Bomba knew at the first touch of those fingers of steel about his
throat.
50. Few could break the grip of the jungle Indian. Only those bred as
Bomba had been among the very wild beasts of that tangled region
could have hoped to free himself of that strangle hold.
With a tremendous heave of his powerful young shoulders Bomba
flung himself upon his back, the Indian half over him. With frantic
fingers the lad tore at that clutch about his throat.
Above, the thunder rumbled dourly. Dim flashes of sheet lightning
served to deepen by contrast the darkness that enveloped the
antagonists.
Strain as he would the lad could not force that hold to break. His
head was reeling, his brain confused and black spots danced before
his glazing eyes.
A flash of lightning brighter than the rest showed him the Indian, on
whose face was an expression of fiendish gloating.
That look was a spur to Bomba’s failing senses. He thought of
Casson, left defenseless with Bomba dead, and by a mighty effort
raised himself and drove his knee with all his strength into the flesh
beneath the ribs of his antagonist.
The blow was a surprise to the Indian, who counted his adversary as
already beaten. He grunted with dismay and pain. For the fraction of
an instant his grip relaxed, and in that instant Bomba had burst the
iron ring about his throat and was on his feet.
With a bellow of rage the savage also sprang upright, whipping out a
short knife from his belt.
But quick as he was, Bomba was quicker. He saw the gleam of the
Indian’s steel, drew his own machete and with one stroke sent his
enemy’s weapon whizzing off into the underbrush.
Like a panther, the Indian sprang upon the white boy, and before
Bomba could strike home with the machete had seized upon the
lad’s hand, striving to bend it backward and possess himself of the
machete.
But if the Indian was strong, so was Bomba. He was fighting for two
lives, his own and Casson’s, and, moreover, one of his fierce rages
51. was upon him; one of those wild bursts of fury that for the moment
gave him the strength of the jaguar, the wile of the fox, the quickness
of the snake.
Bomba was all these in one now, as he fought with the Indian,
straining backward and forward, resisting the pressure upon his knife
arm, striving with all the power in him to drive downward the shining
point of his machete, to sink it to the hilt in his enemy’s flesh.
For some minutes the fierce struggle went on. Then, with a sudden
twist, Bomba broke the Indian’s hold, leaped backward several feet,
and threw his machete.
It would have found its mark had not the savage fallen forward with
the sudden releasing of Bomba’s pressure. The knife grazed his
head. Thrown off his balance, the savage tried to recover himself.
But the slime of mud and leaves made treacherous footing and the
Indian plunged headlong.
Bomba was upon him with the swiftness of a jaguar!
52. CHAPTER III
THE BLAZING CABIN
At such close quarters Bomba could not use his bow, and he dared
not fire the revolver lest it attract the attention of lurking foes.
Rising into the air, he came down with both feet on his enemy’s
head. Then he stamped the head into the mud and ooze till the
savage lay still.
Whether the man breathed or not, Bomba did not stop to inquire. It
was enough that he had been put out of action. The noise of the
struggle, muffled as it had been, might already be drawing others to
the scene. Bomba must act swiftly, if he were to leave the spot alive.
One of his precious minutes he gave to the search for his machete.
With its aid he might still win through to Casson at the hut of Pipina.
By a stroke of good fortune he found the weapon where it had stuck
in the trunk of a tree.
With a smothered cry of elation, Bomba leaped upon it and
wrenched it from its hold. Again and again that knife had saved his
life, and it might do it again before the night was over.
Bomba’s body was bruised, he was dead tired, but his spirit was
unhurt. The thirst of battle was still in him. His blood was hot with it.
Twice to-night he had outwitted his enemies. Nascanora and his half-
brother Tocarora would again realize that he, Bomba, was as
slippery as the cooanaradi and as deadly.
He wasted no time. He set his feet in the direction of the cabin of
Pipina, the squaw, and went stealthily yet swiftly through the jungle.
The storm had felled great trees across his path. Some of these he
climbed over, while he took the smaller ones with a leap. Where the
ground was impassable he swung himself along from creeper to
53. creeper and branch to branch. No inhabitant of the jungle save the
monkeys were as skilled in this method of progress as Bomba, and
he made his way with amazing celerity. Never had that
accomplishment stood him in better stead.
His eyes and ears were alert for the slightest sight or sound that
might forebode danger. But this did not prevent his mind from being
in a tumult of varied emotions.
His most anxious thought was of Casson, Casson alone in the jungle
hut save for Pipina. Again the headhunters sought the life of Casson.
Again was Bomba hunted like the veriest wild beast.
Bitterness welled up in the heart of the lad against these savages,
whom he had never injured except in self-defense. Why was he
doomed to spend his life among these people so alien to him?
Bomba was white. All his yearnings were toward those of his own
race.
Who were his parents? He thought of the picture of the beautiful
woman that had hung in the little back room of Sobrinini’s hut on the
island of snakes. That face had stirred his heart as no other had ever
done. Was the beautiful woman his mother?
Who was he? What had happened to his parents and why had he
become at so early an age the sole companion of old Cody Casson?
He reviewed the strange behavior of the half-mad old woman,
Sobrinini, she who had once been the operatic idol of Europe, she
who had had kings at her feet. Why had she not finished the story of
the man named Bartow, his wife, Laura, and the child they called
Bonny?
Sobrinini had called him, Bomba, by the name of Bartow. She had
thought in her poor twisted mind that Bomba was Bartow. Was it
possible that Bomba was the boy who had once been called Bonny?
Bomba heaved a heavy sigh. Questions, questions always, and no
answers. Cody Casson had the key to the mystery. But poor Casson
must first find the key to that closed door in his mind beyond which
he could not go.
54. His mind in a whirl of unrest and longing, Bomba at last reached the
river which he must cross to reach the hut of Pipina.
The storm had now entirely died away. Only the heavy dripping of
moisture from the foliage betrayed its recent passage. The jungle
was still again with an unearthly stillness. The slight swish made by
Bomba as he swung himself from branch to branch was the only
sound that broke the silence.
Suddenly he paused and hung motionless, arms and legs entwined
about a bunch of creepers. His quick ear had caught a sound other
than the dripping of water on the sodden earth.
It was a slight sound, but Bomba knew at once what had caused it. It
was the faint dip of paddles in the water. The Indians were traveling
upstream. The headhunters of Nascanora were on their way to the
hut of Pipina to spread terror and death. Fortunate if death were all!
Far worse would be the tortures of any captives who might be
carried off alive to make a holiday for the savages who had been left
at home and who would revel in the screams of their victims.
Bomba had been carrying his machete between his teeth. Now he
dropped lightly to the ground, and, with the double-edged knife held
firmly in his grasp, ran swiftly toward the river.
Upon the banks of the stream he paused, listening. Still the dip, dip
of paddles coming upstream. So faint and stealthy was the sound
that it would have been inaudible to most ears other than those of
Bomba.
The lad wasted not an instant, but slipped from the steep bank until
he was waist deep in the sluggish water. The dense foliage of the
jungle trees grew down to the very edge of the stream, flinging its
rank growth out over the water.
Bomba had a canoe of his own concealed in the bushes some
distance up the stream. Had there been time, he would have made
for that, for he well knew the danger of making the river crossing by
fording or swimming. The deadly alligator, or cayman, infested all the
waters of the jungle, and any daring person that ventured to cross
knew that he might pay for the venture with his life.
55. But time was everything to Bomba now. The headhunters were more
to be feared than the cayman. The former were awake. The latter
might be asleep. At all costs, he must make the venture. He must
make haste, if he were to save the life of Casson and that of Pipina.
Bomba had let himself go so gently into the water as scarcely to
make a ripple, and he moved on noiselessly, wading where he could,
but soon reaching the deeper channel where he had to swim. Then
most of the time he swam under water lest his presence be declared
to prying eyes. He was almost as much at home in the water as on
land, and only at long intervals had to come to the surface for air.
But swiftly as he swam, the Indians could paddle more swiftly. And a
terrible fear gripped the lad’s heart as the sound of the paddles grew
ever fainter in the distance.
They would reach the hut first. They would find it undefended and
might attack at once. The worst might have happened before Bomba
could reach the only place he called home.
What he would do when he got there he had not figured out. He
would act as the occasion suggested. He would be but one against
many; but he had been in that position more than once and yet won
the victory.
He swam on swiftly until he was arrested by a sight that brought a
growl of fury to his lips.
Turning a bend in the river, a light assailed Bomba’s eyes, a fearful
light, a light such as the native of the jungle dreads above all others.
It was a dull glow, brightening now and then to a vivid red as the
flames swept skyward.
Bomba groaned and his teeth gritted against each other as he
plunged madly forward. For he knew all too well what had caused
the glare. The hut of Pipina was ablaze!
This was the work of Nascanora’s bucks, their revenge upon a
broken, demented old man who had never harmed any one in his
life!
56. Was Casson in that blazing hut? Was poor Pipina, faithful friend,
caught in that flaming inferno?
Scarcely daring to put these questions to himself, Bomba swam
madly upstream, his one thought now of revenge. He was consumed
by rage. His one desire was to feel the throat of Nascanora beneath
his fingers.
The light was brighter now. The whole jungle was bathed in the
fiendish glow.
Bomba turned toward the bank, but paused abruptly and trod water.
Between him and the shore, blocking his path, was a monster
alligator!
57. CHAPTER IV
TERRIBLE JAWS
At sight of the cayman, Bomba’s heart for a moment seemed to stop
beating.
A wild hope that perhaps the brute was asleep and would not
perceive his presence was quickly dispelled as the lad caught sight
of two fiery eyes fixed upon him. Then the huge mouth opened,
displaying the horrible array of teeth that, if they once closed on the
lad, would bite him in half as easily as a pair of shears would snip a
thread.
Despairingly, Bomba felt for his machete. He knew that it would avail
little except perhaps to wound. It would simply help him to die
fighting.
Then his heart leaped. His feet felt the river bed beneath them! He
had reached the shallower part of the stream! Now he would have a
footing, something that would give him a purchase and enable him to
use his bow and arrows.
Quick as lightning, he unslung the bow from his shoulder and drew
an arrow from its quiver. With one motion he fitted the arrow to the
string and let fly.
The light from the fire gave him what he needed for his aim, and the
arrow entered the eye of the monster and penetrated to the brain.
With a fearful bellow of rage and pain, the great brute leaped half out
of the water and fell back, only to churn the water into a seething
whirlpool. In its wild flounderings the end of its serrated tail caught
Bomba on one of his legs and threw him farther out into the stream.
Bomba did not mind the blow, so full of exultation was he at the
mortal wound he had inflicted on his enemy. But his elation changed
to fear when he saw the scaly back of another alligator breaking the
58. water. The brute had been attracted by the uproar created by its
stricken comrade and was coming swiftly.
Luckily, the bank was not far away, and, putting all his power into his
strokes, the boy swam as he had never swum before. He reached
the shore not a moment too soon, for the hideous jaws snapped
close behind him as he pulled himself up the bank.
The impulse was strong on Bomba to shoot another arrow at the
reptile and send it to join its companion. But arrows were precious
now, and all he had would perhaps be needed for human foes.
So he repressed the impulse and hurried along the bank until he had
come near the fringe of trees that bordered the clearing in which
stood the hut. He could not yet see the hut itself. But to reach it he
would have to make a dash across the clearing.
In the dark he could have eluded the eyes of his enemies, for no
snake could move more silently. But now the open space was
flooded with light. No figures were visible, but he knew that many
eyes were watching from the surrounding woods.
Still he must chance it. He had faced death too often to let it daunt
him now.
Summoning all his strength, he darted out into the open. His first few
bounds carried him fifty feet. Then he dropped to the ground as a
dozen arrows whizzed over his head.
It was upon this that Bomba had counted. He had timed his drop for
just the instant that would allow the startled savages to aim and let
fly.
He was up again on his feet, and before arrows could again be fitted
to strings had gained another fifty feet. Again he repeated his
stratagem, but this time not without scathe, for an arrow grazed his
ankle.
“The arrow may be poisoned,” he thought to himself, as he felt the
twinge of pain. “If it is, this is the end of Bomba.”
He reached the shelter of a tree and whirled behind it. On the side of
the clearing he had just left, one of the headhunters, keen after his
59. prey, had come from behind his shelter.
Like lightning, Bomba fitted an arrow to his string. There was a
twang, a hideous yell, and the savage threw up his hands and fell
headlong.
“There will be one less to fight Bomba,” muttered the lad. “They will
find that Bomba can shoot.”
If any had been inclined to follow the fallen Indian, they had
hesitated when they had seen him drop, and Bomba had a moment’s
breathing space. He flew from behind the tree and, availing himself
of what shelter he could find in his flight, came in sight of what had
been his home.
His heart sank within him. The cabin was a mass of flames. It was
impossible for life to be sustained in that furnace for a minute. If
Casson and Pipina had been trapped there, they were already far
beyond human help. They must be just what the hut itself would be
in a few minutes more, a heap of smoldering ashes.
For a moment Bomba forgot everything save the agony that clutched
at his heart. Then a sound brought him back to the danger that
menaced him personally.
Out from the shelter of the trees, crouched almost double, their
horrible faces illumined by the lurid light of the flames, came a
number of the headhunters.
They approached in a semicircle, cutting off Bomba’s retreat toward
the front and on either side. Back of him was the blazing hut, the
heat from which was already scorching his face and hands.
Bomba felt that he was trapped. His doom seemed sealed. He felt
for the handle of the machete at his belt. He grasped his bow. He
would not allow himself to be taken alive. Better instant death than
the tortures of Nascanora. And he vowed that he would take more
than one of his enemies with him.
He bent his bow, took quick aim and fired. A bronze-skinned buck
clapped a hand to his breast, gave a frightful howl, and fell writhing
in the dust.
60. But before Bomba could fit another arrow to his string there was a
concerted rush and a dozen hands reached out to seize him.
Bomba leaped back quickly and drew his machete. His eyes blazed,
his muscles tensed.
The Indians yelled and leaped forward.
Bang!
A sharp detonation clashed against their eardrums like a crash of
thunder. The force of the explosion shook the earth and flung the
natives to the ground.
Bomba found himself on his face, half-stunned, bewildered.
Mysterious missiles hurtled over his head, exploding in mid-air.
He raised himself cautiously to his knees and saw a sight that
brought hope to his heart.
The Indians were in full retreat, and as they fled they looked over
their shoulders at him fearfully, as though they blamed him for their
discomfiture.
Bomba well knew the mind of the Indian. The cause of the explosion
and the trembling of the earth were unknown to them. So they
reasoned that it must be a spell thrown over them by Bomba, friend
of the old witch doctor, Casson, to destroy them and save himself.
The Indians stopped in their mad flight at the edge of the jungle and
looked back. One of them, more daring than the rest, raised his bow
and took aim.
But before he could release the string one of the flying missiles
struck the would-be slayer, hurling him to the ground.
This was too much. The savages turned terror-stricken and fled from
that scene of mysterious death.
By this time Bomba had realized what must have caused the
explosion. Their little store of powder, so carefully guarded by
Casson and himself, had gone off when reached by the hot breath of
the fire. The flying missiles were the last of the cartridges belonging
61. to his revolver, that wonderful gift of Gillis and Dorn, the white rubber
hunters.
Bruised and shaken, Bomba staggered to his feet, hardly able to
believe his good fortune.
But as he turned back toward the cabin a great wave of desolation
flooded his heart.
There lay the cabin, now a heap of ashes. Were the ashes of
Casson and Pipina also there? Had those faithful ones come there to
their death?
With a sob Bomba threw himself on the ground and abandoned
himself to uncontrolled grief.
This, however, was of short duration. A wild rage welled up in his
heart, rage against the wicked Nascanora and his cruel tribe.
“They shall pay!” the lad cried, leaping to his feet. “For every drop of
Casson’s blood they shall pay! There will yet be wailing in the huts of
Nascanora. It is I, Bomba, who swear it!”
He paused, head upflung, listening.
What was that sound?
62. CHAPTER V
HOW THE INDIANS CAME
Bomba strained his ears and again heard the thing that had startled
him. It was a faint cry, rising and falling like a wail somewhere in the
bushes.
“Help!” came the voice, eerie as that of a banshee in the darkness.
“Help, Bomba! Help!”
Into Bomba’s heart sprang a great joy. This was the voice of Pipina,
the squaw—the voice that he had never expected to hear again. And
where Pipina was, must be Casson!
He was off like a deer in the direction from which the cry had come.
“Bomba hears you,” he called softly. “Bomba is coming.”
“Help!” came the feeble voice again. “Pipina is caught and cannot
get loose. Come quickly.”
Bomba wondered why he did not hear Casson’s voice, if Casson still
lived. But he said nothing and hurried on, hacking a passage through
the undergrowth.
He came nearer and nearer to the wailing woman until, pushing
aside a tangle of vines, he saw her. The moon, following close on the
heels of the tropical storm, was now riding high in the heavens and
shedding a soft luster over the jungle. By its light, Bomba caught
sight of Pipina as she stood holding out helpless hands to him.
She had been caught in a thorn thicket that had cruelly scratched her
hands and arms as she had struggled to free herself. Her wrinkled
face was drawn with pain.
By the deft use of his machete Bomba cleared away the clutching
branches and released her. The old squaw staggered dizzily, and the
lad put this arms about her shoulders to support her.
63. “Casson!” muttered Bomba hoarsely. “Tell me, Pipina! Tell me quick!
Where is Casson?”
The old woman drooped her head and stood there like a bowed
statue of grief, but said nothing until Bomba, mad with anxiety, shook
her gently by the shoulders.
“Do you hear, Pipina? Where is the good white man, Cody Casson,
my friend?”
Then the old woman raised her hands above her head and gave
vent to a wailing, desolate cry.
“Pipina no can tell. Casson her friend, too, good friend. He is gone.”
Bomba’s face darkened and again his heart contracted under the
cold hand of anguish.
“Tell me, Pipina,” he commanded. “Where has he gone? What has
become of him?”
“We sit down and I will tell you,” returned the squaw. “Pipina weak,
sick—”
For answer, Bomba cleared a space and, taking the old woman,
placed her as comfortably as he could with her back resting against
a giant tree.
He sat down opposite her, his arms folded, his glance full upon her
face.
“Now, Pipina, tell Bomba all,” he urged.
The old woman looked about her and shuddered. She wrapped her
skinny arms about her as though they were a garment and had
power to ward off the chill of the night.
“Headhunters—they gone?” she asked fearfully.
“Gone,” said Bomba tersely. “Where is Casson?”
“Bomba make them go away all by himself,” continued the squaw
admiringly. “Bomba great man some day—”
Bomba bent toward her.
64. “Do not talk foolishly, Pipina. Bomba not care about himself. Pipina
tell about Casson.”
The old woman gave her wailing cry and rocked herself back and
forth drearily.
“We have bad time, Casson, Pipina,” she said. “We all alone in hut,
wishing Bomba come. Storm come, but not Bomba. Thunder like
roar of pumas, many pumas.”
“Bomba caught in storm,” explained the lad. “No could come till
storm stopped.”
“Pipina listen for sound of Bomba’s feet,” went on the squaw. “Pipina
afraid. She think danger near. Wish Bomba would come quick.”
She said this, leaning forward, in a quick, hissing whisper. Now she
relaxed against the tree and stared gloomily into the heavy shadows
of the jungle.
“Casson not too good,” she muttered. “Pipina worry about Casson.
Worry hard.”
“What was wrong with Casson?” cried Bomba, exasperated beyond
measure by the slowness with which Pipina got on with her story.
“He very sick,” returned the squaw. “He not right.” She touched her
forehead significantly. “He walk back and forth, back and forth, and
talk to himself. He say: ‘Laura, Laura, dear sweet Laura. Must tell
Bomba. Bartow and Laura and little boy—’”
Bomba caught the arm of the old woman in an eager grip.
“Go on,” he commended. “What else did Casson say? Tell Bomba.”
But Pipina shook her head.
“He not say more,” she said. “Only those words he say again and
again. Then he stop, listen at door of hut, listen and then walk up
and down, up and down.”
“Go on,” cried Bomba.
“Then we hear things. We think you come. We happy. We sing. We
dance. But no, Bomba not come. It is the headhunters that come to
65. try to kill Casson and Pipina—”
Bomba gave a low growl like that of an animal and ground his teeth
together.
“They come.” The voice of the old woman rose again in eerie wailing.
“Casson, Pipina, we close door, push bolt, as Bomba tell us. We
heap things against door. Casson he take down old gun, but it not
work. He put fire stick through hole in hut. He think frighten bucks of
Nascanora.”
Bomba groaned as he saw the picture of old Cody Casson, brave to
the last, defying death, his only weapon a “fire stick” that would not
work.
“It happen quick,” went on Pipina with a helpless shake of her head.
“One, two, three—like that,” with a snap of her bony fingers. “The
headhunters come. They have heads, fresh heads, women, children
heads, on string at waists. They want more heads, Casson’s head,
Pipina’s head. They beat on door. They say: ‘Open. No hurt.
Nascanora friend of Casson.’”
Again came that growl as of an angry jaguar from the clenched teeth
of Bomba.
“Forked tongues! Black hearts!” he snarled. The woman nodded.
“Casson no open door,” she resumed. “He know Nascanora. He say
things. Make big chief mad. He beat more hard on door. He shout:
‘Casson witch doctor. He put a spell on sick people of our tribe.
Nascanora burn Casson and hut of Casson with him.’”
A smoldering fire was in Bomba’s eyes that boded no good to the
chief of the headhunters.
“Then Nascanora bring fire to the hut of Pipina,” went on the squaw.
“His bucks come with heaps of vines and leaves. They wet and not
burn at first. But after they burn, burn hot, and the hut of Pipina begin
to burn too.”
“But you got away, Pipina!” burst in Bomba eagerly. “You got away
from the headhunters and the fire. That was good. But how did you
do it? Tell Bomba. Do not make much words.”
66. The old woman shrugged her shoulders and there was a touch of
pride in her tone as she replied:
“Beneath the hut of Pipina there is a hole, and this hole it lead under
the ground out into the jungle.”
Bomba stared at her.
“A hole!” he exclaimed. “A passage! Why you not tell Bomba?”
The squaw smiled inscrutably.
“None know but Pipina.”
Bomba was listening with the most intense interest and wonder.
“Go on,” he cried, as Pipina paused.
“Pipina take up board in floor of hut,” went on the old woman. “Then
get down and crawl through hole. Casson come too. Long time to
creep through hole. Then come to end. Out into jungle where wet
and cool.”
“Then Casson got out safely?” cried Bomba.
The squaw nodded, and Bomba gratefully took her old wrinkled hand
in his.
“Pipina has saved the life of Casson,” the lad said gravely. “For this
Bomba thanks Pipina. He will never forget.”
The old woman threw her hands above her head, rocking herself
back and forth.
“Ayah, ayah!” she wailed. “Pipina save the life of Casson, but she
lose him after. For when Pipina look around Casson is gone!”
67. Welcome to our website – the ideal destination for book lovers and
knowledge seekers. With a mission to inspire endlessly, we offer a
vast collection of books, ranging from classic literary works to
specialized publications, self-development books, and children's
literature. Each book is a new journey of discovery, expanding
knowledge and enriching the soul of the reade
Our website is not just a platform for buying books, but a bridge
connecting readers to the timeless values of culture and wisdom. With
an elegant, user-friendly interface and an intelligent search system,
we are committed to providing a quick and convenient shopping
experience. Additionally, our special promotions and home delivery
services ensure that you save time and fully enjoy the joy of reading.
Let us accompany you on the journey of exploring knowledge and
personal growth!
ebookfinal.com