In Example 21-1, when you retrieve the information from the collection, you retrieve the entire Book
object, but you output only the title and author. That's somewhat wasteful, because you're retrieving more information than you actually need. Since you need just the title and author, it would be preferable to be able to say something like this:
IEnumerable<Book> resultsAuthor = from testBook in bookList where testBook.Author == "Jesse Liberty" select testBook.Title, testBook.Author;
That construction will cause an error, though, because your query can return only one type of object. You could define a new class—say, bookTitleAuthor
—to hold just the two bits of information you need, but that would also be wasteful, because the class would get used in only one spot in your program, right here when you retrieve and then output the data. Instead, you can just define a new class on the fly, like this:
IEnumerable<Book> resultsAuthor = from testBook in bookList where testBook.Author == "Jesse Liberty" select new { testBook.Title, testBook.Author };
Notice that this class doesn't have a name; it doesn't really need one, because you're using it only in this one spot. Therefore, this feature is called an anonymous type. Based on the select
statement, the compiler determines the number and types of the properties for the class (two strings, in this case), and creates the class accordingly.
This code won't work yet, though. You're assigning the results of the query (now a collection of anonymous objects) to a collection of type <Book>
. Obviously, that's a type mismatch, and you'll need to change the type. But what do you change it to, if you don't know the name of the anonymous type? That's where implicitly typed variables come in. As we mentioned way back in Chapter 3, C# has the ability to infer the type of a variable based on the value you're assigning to it. Even though you don't know the name of the anonymous type, the compiler has assigned it as an identifier, and can recognize that type when it's used. Therefore, your new query looks like this:
var resultsAuthor = from testBook in bookList where testBook.Author == "Jesse Liberty" select new { testBook.Title, testBook.Author };
Now resultsAuthor
is a collection of anonymous objects, and the compiler is perfectly fine with that. All you need to know is that resultsAuthor
is a collection that implements IEnumerable
, and you can go ahead and use it to output the results:
Console.WriteLine("Books by Jesse Liberty:"); foreach (var testBook in resultsAuthor) { Console.WriteLine("{0}, by {1}", testBook.Title, testBook.Author); }
We've replaced the Book
type in the foreach
loop with var
, but the compiler still knows what type testBook
is, because it's a member of the collection resultsAuthor
, and the compiler knows what type that is, even if you don't.
These changes are shown in Example 21-2, although we've omitted the Book
class definition and the creation of the List
for space, because those haven't changed.
Example 21-2. With anonymous types and implicitly typed variables, you can use the results of a query even when they're a complex type
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Example_21_2_ _ _ _Anonymous_Types { // simple book class public class Book { ... } class Program { static void Main(string[] args) { List<Book> bookList = new List<Book> { ... }; // find books by Jesse Liberty var resultsAuthor = from testBook in bookList where testBook.Author == "Jesse Liberty" select new { testBook.Title, testBook.Author }; Console.WriteLine("Books by Jesse Liberty:"); foreach (var testBook in resultsAuthor) { Console.WriteLine("{0}, by {1}", testBook.Title, testBook.Author); } } } }