An Extensive Examination of LINQ: Extension Methods, Implicitly Typed Variables, and Object Initializers
By Scott Mitchell
Introduction
One of the more substantive additions to the .NET Framework 3.5 and C# 3.0 and Visual Basic 9 languages was LINQ, a set of classes along with language enhancements that allow developers to use a common library and SQL-like query syntax to work with common data stores. The initial article in this series, An Introduction to LINQ, provided an overview of LINQ and its core pieces: the standard query operators, the language extensions that allow for LINQ's query syntax, and LINQ providers. We also looked at some simple LINQ examples using both the standard query operators and the query syntax.
LINQ's standard query operators - Select, Where, OrderBy, Average, and so on - can be used as if they were instance methods
of any object that implements IEnumerable<T>. For example, given a string array named FileNames we can determine how many strings in the array start
with the letter "S" by using the Where and Count standard query operators like so:
string[] FileNames = { ... };
|
The Where method and Count methods look like they are members of the Array class. However, they are defined in the
Enumerable class in the
System.Linq namespace as extension methods on the IEnumerable<T>
interface. Also note the syntax that is used to specify the input parameter for the Where method: name => name.StartsWith("S"). This syntax is a lambda
expression and provides a shorthand notation for developers to define a function.
This installment (and the next one) explore the language enhancements Microsoft made to C# 3.0 and Visual Basic 9 in more depth. (C# 3.0 and Visual Basic 9 are, at the time of this writing, the most recent versions of these two programming languages. They are the versions that were released with the .NET Framework 3.5 and Visual Basic 2008.) The key language enhancements that make LINQ possible include: extension methods; implicitly typed variables; object initializers; lambda expressions; and anonymous types. This article explores the first three of these language extensions; the latter two will be covered in the next installment. Read on to learn more!
Extension Methods
Every developer has, at one time or another, needed to incorporate some extra functionality in one of the .NET Framework base types. Consider string processing. While the
String class in the .NET Framework includes a plethora of methods for parsing, trimming, and dissecting strings, there may some very specific string parsing
logic needed for your application. Such custom functionality is commonly implemented by creating a "wrapper" class with methods that take in a string and return the parsed/processed
string. Imagine that you needed a method that added a space after a letter if it was immediately followed by an uppercase letter, thereby transforming a string like "DisplayProduct"
to "Display Product". The following method placed in a wrapper class would suffice.
public class StringHelpersCS
|
(A Visual Basic version of this method is included in the code available for download at the end of this article.)
The above method starts by creating a StringBuilder named output. It then loops through each character in input and tacks it onto
output. However, if the character under scrutiny is an uppercase character (and it's not the first character in the string), a space is added to output
before the character. This method could be called from the application using the following syntax:
string pageFileNameLessExtension = "DisplayProduct";
|
While wrapper classes offer a simple approach to encapsulate commonly used functionality, there is no tight association between the wrapper class's functionality and the
type its methods operate on. In order to use the wrapper class, a developer must first know of its existence; he must dig into the various methods to see how they operate.
Ideally, the AddSpacesAfterCaseChange method could be applied directly to a string as if it were one of the string members, using syntax like the following:
string pageFileNameLessExtension = "DisplayProduct";
|
This syntax makes it clear that the AddSpacesAfterCaseChange modifies a string. It also would allow for IntelliSense, causing the AddSpacesAfterCaseChange
to show up in the IntelliSense drop-down after typing pageFileNameLessExtension and typing period.
The good news is that such functionality is possible in C# 3.0 and Visual Basic 9 via extension methods. In a nutshell, you can create a specially-formatted class
and methods that are denoted to apply to a specific type (such as String). Once this method has been created, you can use it like it was an instance method on
the type it extends, and Visual Studio includes the extension method in the IntelliSense drop-down. For example, to turn the AddSpacesAfterCaseChange method into an
extension method on the String class we would only need to make two small changes. First, we would need to mark the class as static; second,
we need to pass in the type the extension method operates on as the first parameter to the method using the this keyword.
public static class StringHelpersCS
|
Now that the AddSpacesAfterCaseChange method is an extension method we can use it as if it was a method defined on the String class. We also get rich IntelliSense
support in Visual Studio, as the following screen shot shows.
Keep in mind that extension methods are syntactic sugar. At compile-time the extension methods that are invoked
like instance methods are, in actuality, called using the wrapper class-style syntax - pageFileNameLessExtension.AddSpacesAfterCaseChange() is translated
into StringHelpers.AddSpacesAfterCaseChange(pageFileNameLessExtension). Moreover, you don't have to use extension methods like an instance method. You can still
invoke the method using the wrapper class syntax. For more background and instructions on creating extension methods, including examples in Visual Basic, be sure to read
Extending Base Type Functionality with Extension Methods.
LINQ makes heavy use of extension methods. Recall that the myriad of standard query operators - Select, Where, OrderBy, Average,
Count, and so on - work on objects that implement the IEnumerable<T> interface. These methods are defined in the
Enumerable class in the
System.Linq namespace as extension methods, thereby allowing them to be used like instance
methods against any object that implements IEnumerable<T>.
Without extension methods, the LINQ standard query operator syntax would be less readable and wouldn't enjoy the IntelliSense support extension methods offer. To see the difference in readability, compare the following two code snippets, which are functionally identical. The first one uses the wrapper class syntax in calling the standard query operators.
// C#: Get the average of the odd Fibonacci numbers in the series...
|
Whereas the second one uses the extension method syntax.
// C#: Get the average of the odd Fibonacci numbers in the series...
|
The second snippet is more readable and is easier to see what operations are being performed and on what object as they are defined left to right (the fibNum array
first has the Where operator applied, then the Average operator), whereas with the wrapper class syntax you have to work from the inside out.
Implicitly Typed Variables
All variables in .NET languages have an associated type. The type of a variable determines what values it can store and what properties and methods are available, and is specified when defining a variable. In the preceding example the
fibNum variable's type is an array of integers, as was defined as such when declaring the
variable (int[] fibNum and Dim fibNum() As Integer). With C# 3.0 and Visual Basic 9 you no long have to explicitly define a variable's type. Instead,
you can use implicit typing. With implicit typing the variable still has a type, and that type still dictates what values the variable can store and the available properties
and methods. However, with implicit typing the variable type is inferred dynamically based on the variable's initially assigned value.
To create an implicitly typed variable in C# use the var keyword; in VB, use the Dim keyword like usual but do not specify a type. The following
snippet shows implicitly typed variables in use.
// C#
|
The types for variables message and displayCount are not defined; rather, their types are inferred by the values initially assigned to them. You can
see that these variables have known types by hovering your mouse over the variable name. Visual Studio shows a tool tip that includes the variable's type.
What's important to keep in mind is that these implicitly typed variables are not loosely-typed. For example, you cannot change the type of the displayCount variable
further down in the code by assigning it a different typed value. Rather, the type of the variable is fixed and determined by the initial value assigned to it. For this
reason you must specify an initial value when using implicitly typed variables. That is, you cannot do the following:
// C# - This will not compile
|
Implicitly typed variables are another example of syntactic sugar. At compile-time the implicitly typed code is turned into explicitly typed code. If implicitly typed variables seem a bit like a child's toy at this point - kind of neat and fun to play with, but not really all that useful in the real world - that's understandable. For simple types, implicitly typed variables do nothing more than save you a few keystrokes. Where implicitly typed variables are quite useful is when working with anonymous types. Anonymous types, as the name implies, are objects whose type is not explicitly defined. Consequently, you cannot create a variable of the type to hold the value of an object that is anonymously typed, and in that case implicitly-typed variables are a must. If this all sounds confusing, don't worry - we'll explore anonymous types in detail in the subsequent article in this series.
Object Initializers
A common pattern in object-oriented programming languages (like C# and Visual Basic) involves instantiating an object and immediately assigning values to a number of its properties. For example, given an
Employee class with properties like EmployeeID, Name, Salary, and so forth, there might
be several places where we have code like the following:
Employee emp = new Employee();
|
This syntax is a bit verbose and lacks the initialization syntax that is available with simpler types. With integers, strings, Booleans, and the like we can specify the value of the variable when creating it:
int displayCount = 5;
|
Wouldn't it be nice to have the same flexibility with objects? The good news is that such functionality is now possible with C# 3.0 and Visual Basic 9. The following code
shows how to create an Employee object in C# and specify values for some of its properties, all in the same statement.
Employee emp = new Employee
|
The syntax for object initialization in Visual Basic is a little bit different. In VB you need to use the With keyword and prefix the property names with a period.
Moreover, the With keyword and property assignments must be within the same statement, meaning they must all be on the same line of code or, if on different lines, separated
by the statement continuation character, the underscore (_).
Dim emp As New Employee With { _
|
The object initialization syntax allows for tighter and more readable syntax, but is especially useful when constructing anonymous types, a topic we'll look at in the subsequent installment.
Conclusion
The core functionality of LINQ is implemented in classes in the
System.Linq namespace using syntax and technologies present in the .NET 2.0 Framework. However,
LINQ's query syntax is possible due to a number of language enhancements made to C# 3.0 and Visual Basic 9. This article explored three of these language enhancements:
extension methods, which allows for a method defined in one class to appear as an instance method in another class; implicitly typed variables, which are variables whose type
is determined by the value they are assigned; and object initializers, which provide a shorthand notation for assigning property values for an instantiated object. There are
two additional important language enhancements that we have yet to explore - lambda expressions and anonymous types. We'll examine these two language enhancements in the next
article in this series.
Happy Programming!
Attachments:
Further Reading