
Pro CSharp 2008 And The .NET 3.5 Platform [eng]
.pdf
442 CHAPTER 13 ■ C# 2008 LANGUAGE FEATURES
// Reflect over what the compiler generated.
ReflectOverAnonymousType(myCar);
Console.ReadLine();
}
Check out the output shown in Figure 13-4.
Figure 13-4. Anonymous types are represented by a compiler-generated class type.
First of all, notice that in this example, the myCar object is of type <>f__AnonymousType0`3 (your name may differ). Remember that the assigned type name is completely determined by the compiler and is not directly accessible in your C# code base.
Perhaps most important, notice that each name/value pair defined using the object initialization syntax is mapped to an identically named read-only property and a corresponding private read-only backing field. The following C# code approximates the compiler-generated class used to represent the myCar object (which again can be verified using tools such as reflector.exe or ildasm.exe):
internal sealed class <>f__AnonymousType0<<Color>j__TPar, <Make>j__TPar, <CurrentSpeed>j__TPar>
{
// Read-only fields
private readonly <Color>j__TPar <Color>i__Field;
private readonly <CurrentSpeed>j__TPar <CurrentSpeed>i__Field; private readonly <Make>j__TPar <Make>i__Field;
// Default constructor
public <>f__AnonymousType0(<Color>j__TPar Color, <Make>j__TPar Make, <CurrentSpeed>j__TPar CurrentSpeed);
// Overridden methods
public override bool Equals(object value); public override int GetHashCode();
public override string ToString();
// Read-only properties
public <Color>j__TPar Color { get; }
public <CurrentSpeed>j__TPar CurrentSpeed { get; } public <Make>j__TPar Make { get; }
}
The Implementation of ToString() and GetHashCode()
All anonymous types automatically derive from System.Object and are provided with an overridden version of Equals(), GetHashCode(), and ToString(). The ToString() implementation simply builds a string from each name/value pair, for example:

CHAPTER 13 ■ C# 2008 LANGUAGE FEATURES |
443 |
public override string ToString()
{
StringBuilder builder = new StringBuilder(); builder.Append("{ Color = "); builder.Append(this.<Color>i__Field); builder.Append(", Make = "); builder.Append(this.<Make>i__Field); builder.Append(", CurrentSpeed = "); builder.Append(this.<CurrentSpeed>i__Field); builder.Append(" }");
return builder.ToString();
}
The GetHashCode() implementation computes a hash value using each anonymous type’s member variables as input to the System.Collections.Generic.EqualityComparer<T> type. Using this implementation of GetHashCode(), two anonymous types will yield the same hash value if (and only if) they have the same set of properties that have been assigned the same values. Given this implementation, anonymous types are well suited to be contained within a Hashtable container.
The Semantics of Equality for Anonymous Types
While the implementation of the overridden ToString() and GetHashCode() methods is fairly straightforward, you may be wondering how the Equals() method has been implemented. For example, if we were to define two “anonymous cars” variables that specify the same name/value pairs, would these two variables be considered equal or not? To see the results firsthand, update your Program type with the following new method:
static void EqualityTest()
{
// Make 2 anonymous classes with identical name/value pairs.
var firstCar = new { Color = "Bright Pink", Make = "Saab", CurrentSpeed = 55 }; var secondCar = new { Color = "Bright Pink", Make = "Saab", CurrentSpeed = 55 };
//Are they considered equal when using Equals()? if (firstCar.Equals(secondCar))
Console.WriteLine("Same anonymous object!"); else
Console.WriteLine("Not the same anonymous object!");
//Are they considered equal when using ==?
if (firstCar == secondCar) Console.WriteLine("Same anonymous object!");
else
Console.WriteLine("Not the same anonymous object!");
// Are these objects the same underlying type?
if (firstCar.GetType().Name == secondCar.GetType().Name) Console.WriteLine("We are both the same type!");
else
Console.WriteLine("We are different types!");
// Show all the details.
Console.WriteLine();
ReflectOverAnonymousType(firstCar);
ReflectOverAnonymousType(secondCar);
}

444 CHAPTER 13 ■ C# 2008 LANGUAGE FEATURES
Assuming you have called this method from within Main(), Figure 13-5 shows the (somewhat surprising) output.
Figure 13-5. The equality of anonymous types
When you run this test code, you will see that the first conditional test where you are calling Equals() returns true, and therefore the message “Same anonymous object!” prints out to the screen. This is because the compiler-generated Equals() method makes use of value-based semantics when testing for equality (e.g., checking the value of each field for the objects being compared).
However, the second conditional test (which makes use of the C# equality operator, ==) prints out “Not the same anonymous object!”, which may seem at first glance to be a bit counterintuitive. This is due to the fact that anonymous types do not receive overloaded versions of the C# equality operators (== and !=). Given this, when you test for equality of anonymous types using the C# equality operators (rather than the Equals() method), the references, not the values maintained by the objects, are being tested for equality. Recall from Chapter 12 that this is the default behavior for all class types until you overload the operators directly in your code (something that is not possible for anonymous types, as you don’t define the type!).
Last but not least, in our final conditional test (where we are examining the underlying type name), we find that the anonymous types are instances of the same compiler-generated class type (in this example, <>f__AnonymousType0`3), due to the fact that firstCar and secondCar have the same properties (Color, Make, and CurrentSpeed).
This illustrates an important but subtle point: the compiler will only generate a new class definition when an anonymous type contains unique names of the anonymous type. Thus, if you were to declare identical anonymous types (again, meaning the same names) within the same assembly, the compiler only generates a single anonymous type definition.
Anonymous Types Containing Anonymous Types
It is possible to create an anonymous type that is composed of additional anonymous types. For example, assume you wish to model a purchase order that consists of a timestamp, a price point, and the automobile purchased. Here is a new (slightly more sophisticated) anonymous type representing such an entity:
// Make an anonymous type that is composed of another. var purchaseItem = new {
TimeBought = DateTime.Now,

CHAPTER 13 ■ C# 2008 LANGUAGE FEATURES |
445 |
ItemBought = new {Color = "Red", Make = "Saab", CurrentSpeed = 55},
Price = 34.000};
ReflectOverAnonymousType(purchaseItem);
At this point, you should understand the syntax used to define anonymous types, but you may still be wondering exactly where (and when) to make use of this new language feature. To be blunt, the use of anonymous type declarations should be used sparingly, typically only when making use of the LINQ technology set (see Chapter 14). You would never want to abandon the use of strongly typed classes/structures simply for the sake of doing so, given anonymous types’ numerous limitations, which include the following:
•You don’t control the name of the anonymous type.
•Anonymous types always extend System.Object.
•The fields and properties of an anonymous type are always read-only.
•Anonymous types cannot support events, custom methods, custom operators, or custom overrides.
•Anonymous types are always implicitly sealed.
•Anonymous types are always created using the default constructor.
However, when programming with the LINQ technology set, you will find that in many cases this syntax can be very helpful when you wish to quickly model the overall shape of an entity rather than its functionality.
■Source Code The AnonymousTypes project can be found under the Chapter 13 subdirectory.
Summary
C# 2008 provides a number of very interesting features that bring C# into the family of functional languages. This chapter walked you through each of the core updates, beginning with the notion of implicitly typed local variables. While the vast majority of your local variables will not need to be declared with the var keyword, as you will see in the next chapter doing so can greatly simplify your interactions with the LINQ family of technologies.
This chapter also described the role of automatic properties, partial methods, extension methods (which allow you to add new functionality to a compiled type), and the syntax of object initialization (which can be used to assign property values at the time of construction).
The chapter wrapped up by examining the use of anonymous types. This language feature allows you to define the “shape” of a type rather than its functionality. This can be very helpful when you need to model a type for limited usage within a program, given that a majority of the workload is offloaded to the compiler.



448 CHAPTER 14 ■ AN INTRODUCTION TO LINQ
Table 14-1. Ways to Manipulate Various Types of Data
The Data We Want |
How to Obtain It |
Relational data |
System.Data.dll, System.Data.SqlClient.dll, etc. |
XML document data |
System.Xml.dll |
Metadata tables |
The System.Reflection namespace |
Collections of objects |
System.Array and the System.Collections/System.Collections. |
|
Generic namespaces |
|
|
Of course, nothing is wrong with these approaches to data manipulation. In fact, when programming with .NET 3.5/C# 2008, you can (and will) certainly make direct use of ADO.NET, the XML namespaces, reflection services, and the various collection types. However, the basic problem is that each of these APIs is an island unto itself, which offers very little in the way of integration. True, it is possible (for example) to save an ADO.NET DataSet as XML, and then manipulate it via the System.Xml namespaces, but nonetheless, data manipulation remains rather asymmetrical.
The LINQ API is an attempt to provide a consistent, symmetrical manner in which programmers can obtain and manipulate “data” in the broad sense of the term. Using LINQ, we are able to create directly within the C# programming language entities called query expressions. These query expressions are based on numerous query operators that have been intentionally designed to look and feel very similar (but not quite identical) to a SQL expression.
The twist, however, is that a query expression can be used to interact with numerous types of data—even data that has nothing to do with a relational database. Specifically, LINQ allows query expressions to manipulate any object that implements the IEnumerable<T> interface (directly or indirectly via extension methods), relational databases, DataSets, or XML documents in a consistent manner.
■Note Strictly speaking, “LINQ” is the term used to describe this overall approach to data access. LINQ to Objects is LINQ over objects implementing IEnumerable<T>, LINQ to SQL is LINQ over relational data, LINQ to DataSet is a superset of LINQ to SQL, and LINQ to XML is LINQ over XML documents. In the future, you are sure to find other APIs that have been injected with LINQ functionality (in fact, there are already other LINQ-centric projects under development at Microsoft).
LINQ Expressions Are Strongly Typed and Extendable
It is also very important to point out that a LINQ query expression (unlike a traditional SQL statement) is strongly typed. Therefore, the C# compiler will keep us honest and make sure that these expressions are syntactically well formed. On a related note, query expressions have metadata representation within the assembly that makes use of them. Tools such as Visual Studio 2008 can use this metadata for useful features such as IntelliSense, autocompletion, and so forth.
Also, before we dig into the details of LINQ, one final point is that LINQ is designed to be an extendable technology. While this initial release of LINQ is targeted for relational databases/ DataSets, XML documents, and objects implementing IEnumerable<T>, third parties can incorporate new query operators (or redefine existing operators) using extension methods (see Chapter 13) to account for addition forms of data.

CHAPTER 14 ■ AN INTRODUCTION TO LINQ |
449 |
■Note Before you continue reading over this chapter, I wholeheartedly recommend that you first feel comfortable with the material presented in Chapter 13 (which covered C# 2008 specific constructs). As you will see, LINQ programming makes use of several of the new C# features to simplify coding tasks.
The Core LINQ Assemblies
As mentioned in Chapter 2, the New Project dialog of Visual Studio 2008 now has the option of selecting which version of the .NET platform you wish to compile against, using the drop-down list box mounted on the upper-right corner. When you opt to compile against the .NET Framework 3.5, each of the project templates will automatically reference the key LINQ assemblies. For example, if you were to create a new .NET 3.5 Console Application, you will find the assemblies shown in Figure 14-1 visible within the Solution Explorer.
Figure 14-1. .NET 3.5 project types automatically reference key LINQ assemblies.
Table 14-2 documents the role of the core LINQ-specific assemblies.
Table 14-2. Core LINQ-centric Assemblies
Assembly |
Meaning in Life |
System.Core.dll |
Defines the types that represent the core LINQ API. This |
|
is the one assembly you must have access to. |
System.Data.Linq.dll |
Provides functionality for using LINQ with relational |
|
databases (LINQ to SQL). |
System.Data.DataSetExtensions.dll |
Defines a handful of types to integrate ADO.NET types |
|
into the LINQ programming paradigm (LINQ to DataSet). |
System.Xml.Linq.dll |
Provides functionality for using LINQ with XML |
|
document data (LINQ to XML). |
|
|
When you wish to do any sort of LINQ programming, you will at the very least need to import the System.Linq namespace (defined within System.Core.dll), which is typically accounted for by new Visual Studio 2008 project files; for example, here is the starting code for a new .NET 3.5 Console Application project:

450CHAPTER 14 ■ AN INTRODUCTION TO LINQ
using System;
using System.Collections.Generic; using System.Linq;
using System.Text;
namespace MyConsoleApp
{
class Program
{
static void Main(string[] args)
{
}
}
}
A First Look at LINQ Query Expressions
To begin examining the LINQ programming model, let’s build simple query expressions to manipulate data contained within various arrays. Create a .NET 3.5 Console Application named LinqOverArray, and define a static helper method within the Program class named QueryOverStrings(). In this method, create a string array containing six or so items of your liking (here, I listed out a batch of video games I am currently attempting to finish).
static void QueryOverStrings()
{
// Assume we have an array of strings.
string[] currentVideoGames = {"Morrowind", "BioShock", "Half Life 2: Episode 1", "The Darkness",
"Daxter", "System Shock 2"}; Console.ReadLine();
}
Now, update Main() to invoke QueryOverStrings():
static void Main(string[] args)
{
Console.WriteLine("***** Fun with LINQ *****\n");
QueryOverStrings();
Console.ReadLine();
}
When you have any array of data, it is very common to extract a subset of items based on a given requirement. Maybe you want to obtain only the items with names that contain a number (e.g., System Shock 2 and Half Life 2: Episode 1), have more than some number of characters, or don’t have embedded spaces (e.g., Morrowind). While you could certainly perform such tasks using members of the System.Array type and a bit of elbow grease, LINQ query expressions can greatly simplify the process.
Going on the assumption that we wish to obtain a subset from the array that contains items with names consisting of more than six characters, we could build the following query expression:
static void QueryOverStrings()
{
// Assume we have an array of strings.
string[] currentVideoGames = {"Morrowind", "BioShock", "Half Life 2: Episode 1", "The Darkness",
"Daxter", "System Shock 2"};
