Language Integration
Language integration is a fundamental aspect of LINQ. The
most visible part is the query expression feature, which is present in C# 3.0
and Visual Basic 9.0. It allows you to write code such as the following:
instead of writing this code:
Many people call this simplification syntax
sugaring because it is just a simpler way to write code that defines a
query over data. However, there is more to it than that. Many language
constructs and syntaxes are necessary to support what seems to be just a few
lines of code that query data. Under the cover of this simple query expression
are local type inference, extension methods, lambda expressions, object
initialization expressions, and anonymous types. All of these features are
useful by themselves, but if you look at the overall picture you should see
that there is a big step in two directions: one moving to a more declarative
style of coding, and one lowering the impedance mismatch between data and code.
Declarative Programming
What are the differences between an SQL query and an
equivalent C# 2.0 or Visual Basic 8.0 program that filters data contained in
native storage (such as a table for SQL or an array for C# or Visual Basic)?
In SQL, you can write the following:
In C#, you would probably write this:
C# code takes longer to write and read. But the most important
consideration is expressivity. In SQL, you describe what
you want. In C#, you describe how to obtain the
expected result. In SQL, the selection of the best algorithm to implement the
how (which is more explicitly dealt with in C#) is the responsibility
of the query engine. This is because the SQL query engine has more freedom to
apply optimizations than a C# compiler, which has many more constraints on the
how.
|
Note |
The previous C# code sample could be written in .NET 2.0
using a Find predicate, but for many developers the
required anonymous delegate syntax is not very easy to use. However, the
samples are merely illustrative of the different programming paradigms.
|
LINQ enables a more declarative style of coding for C# and Visual
Basic. A LINQ query describes operations on data through a declarative
construct instead of an iterative one. LINQ allows the intentions of
programmers to be made more explicit, and this knowledge of programmer intent
is fundamental to obtaining a higher level of services from the underlying
framework. For example, think about parallelization. An SQL query can be split
into several concurrent operations, simply because it does not place any
constraint on the kind of table scan algorithm applied. A C# foreach
loop is harder to split into several loops over different parts of an array
that could be executed in parallel by different processors.
|
More Info |
Parallel LINQ (PLINQ) is a research project on the
parallelism that can be obtained by writing code with LINQ.
|
Declarative programming can take advantage of services offered by
compilers and frameworks, and in general it is easier to read and maintain.
This single “feature” can be the most important one because it boosts
programmers’ productivity. For example, suppose that you want to get a list of
all static methods available in the current application domain that return an
IEnumerable<T> interface. You can use LINQ to write a query over
Reflection:
The equivalent C# code that handles data is longer to write, harder
to read, and probably more error prone. You can see a possible version that is
not particularly optimized below>.
Listing 1-10: C#
code equivalent to a LINQ query over Reflection
List<String> results = new List<string>();
foreach( var assembly in AppDomain.CurrentDomain.GetAssemblies()) {
foreach( var type in assembly.GetTypes() ) {
foreach( var method in type.GetMethods()) {
if (method.IsStatic &&
method.ReturnType.GetInterface("IEnumerable`1") != null) {
string fullName = String.Format( "{0}.{1}",
method.DeclaringType.Name,
method.Name );
if (results.IndexOf( fullName ) < 0) {
results.Add( fullName );
}
}
}
}
}
results.Sort();
Type Checking
Another important aspect of language integration is type
checking. Whenever data is manipulated by LINQ, no unsafe cast is necessary.
The short syntax of a query expression has no compromises with type checking:
data is always strongly typed, including both the queried collections and the
single entities that are read and returned.
The type checking of the languages that support LINQ
(currently C# 3.0 and Visual Basic 9.0) is preserved even when LINQ-specific
features are used. This enables the use of Visual Studio features such as
IntelliSense and Refactoring, even with LINQ queries. These Visual Studio
features are very important for programmers’ productivity.
Transparency Across Different Type
Systems
When you think about the type system of the Microsoft .NET
Framework and the type system of SQL Server, you realize that they are
different. Using LINQ, you give precedence to the .NET type system because it
is the one supported by any language that hosts a LINQ query. However, most of
your data will be saved in a relational database, and it is necessary to
convert many types of data between these two worlds. LINQ handles this
conversion for you automatically, making the differences in type systems almost
completely transparent to the programmer.