Thư viện tri thức trực tuyến
Kho tài liệu với 50,000+ tài liệu học thuật
© 2023 Siêu thị PDF - Kho tài liệu học thuật hàng đầu Việt Nam

Apress pro LINQ Language Integrated Query in C# 2008 phần 2 ppsx
Nội dung xem thử
Mô tả chi tiết
CHAPTER 2 ■ C# 3.0 LANGUAGE ENHANCEMENTS FOR LINQ 33
This could even be rewritten as the following:
Enumerable enumerable = {"one", "two", "three"};
Enumerable finalEnumerable = enumerable
.Where(lX1)
.Where(lX2)
.Where(lX3);
Wow, that’s much easier to read. You can now read the statement from left to right, top to bottom.
As you can see, this syntax is very easy to follow once you understand what it is doing. Because of this,
you will often see LINQ queries written in this format in much of the LINQ documentation and in this
book.
Ultimately what you need is the ability to have a static method that you can call on a class
instance. This is exactly what extension methods are and what they allow. They were added to C# to
provide a syntactically elegant way to call a static method without having to pass the method’s first
argument. This allows the extension method to be called as though it were a method of the first argument, which makes chaining extension method calls far more readable than if the first argument was
passed. Extension methods assist LINQ by allowing the Standard Query Operators to be called on the
IEnumerable<T> interface.
■Note Extension methods are methods that while static can be called on an instance (object) of a class rather
than on the class itself.
Extension Method Declarations and Invocations
Specifying a method’s first argument with the this keyword modifier will make that method an
extension method.
The extension method will appear as an instance method of any object with the same type as the
extension method’s first argument’s data type. For example, if the extension method’s first argument
is of type string, the extension method will appear as a string instance method and can be called on
any string object.
Also keep in mind that extension methods can only be declared in static classes.
Here is an example of an extension method:
namespace Netsplore.Utilities
{
public static class StringConversions
{
public static double ToDouble(this string s) {
return Double.Parse(s);
}
public static bool ToBool(this string s) {
return Boolean.Parse(s);
}
}
}
Notice that both the class and every method it contains are static. Now you can take advantage
of those extension methods by calling the static methods on the object instances as shown in
Listing 2-15. Because the ToDouble method is static and its first argument specifies the this keyword,
ToDouble is an extension method.
Rattz_789-3C02.fm Page 33 Tuesday, October 16, 2007 2:19 PM
34 CHAPTER 2 ■ C# 3.0 LANGUAGE ENHANCEMENTS FOR LINQ
Listing 2-15. Calling an Extension Method
using Netsplore.Utilities;
double pi = "3.1415926535".ToDouble();
Console.WriteLine(pi);
This produces the following results:
3.1415926535
It is important that you specify the using directive for the Netsplore.Utilities namespace,
otherwise the compiler will not find the extension methods and you will get compiler errors such as
the following:
'string' does not contain a definition for 'ToDouble' and no extension method
'ToDouble' accepting a first argument of type 'string' could be found (are you
missing a using directive or an assembly reference?)
As mentioned previously, attempting to declare an extension method inside a nonstatic class is
not allowed. If you do so, you will see a compiler error like the following:
Extension methods must be defined in a non-generic static class
Extension Method Precedence
Normal object instance methods take precedence over extension methods when their signature
matches the calling signature.
Extension methods seem like a really useful concept, especially when you want to be able to
extend a class you cannot, such as a sealed class or one for which you do not have source code. The
previous extension method examples all effectively add methods to the string class. Without extension methods, you couldn’t do that because the string class is sealed.
Partial Methods
Recently added to C# 3.0, partial methods add a lightweight event-handling mechanism to C#. Forget
the conclusions you are more than likely drawing about partial methods based on their name. About the
only thing partial methods have in common with partial classes is that a partial method can only
exist in a partial class. In fact, that is rule 1 for partial methods.
Before I get to all of the rules concerning partial methods let me tell you what they are. Partial
methods are methods where the prototype or definition of the method is specified in the declaration
of a partial class, but an implementation for the method is not provided in that same declaration of
the partial class. In fact, there may not be any implementation for the method in any declaration of
that same partial class. And if there is no implementation of the method in any other declaration for
the same partial class, no IL code is emitted by the compiler for the declaration of the method, the
call to the method, or the evaluation of the arguments passed to the method. It’s as if the method
never existed.
Rattz_789-3C02.fm Page 34 Tuesday, October 16, 2007 2:19 PM
CHAPTER 2 ■ C# 3.0 LANGUAGE ENHANCEMENTS FOR LINQ 35
Some people do not like the term “partial method” because it is somewhat of a misnomer due
to their behavior when compared to that of a partial class. Perhaps the method modifier should have
been ghost instead of partial.
A Partial Method Example
Let’s take a look at a partial class containing the definition of a partial method in the following class
file named MyWidget.cs:
The MyWidget Class File
public partial class MyWidget
{
partial void MyWidgetStart(int count);
partial void MyWidgetEnd(int count);
public MyWidget()
{
int count = 0;
MyWidgetStart(++count);
Console.WriteLine("In the constructor of MyWidget.");
MyWidgetEnd(++count);
Console.WriteLine("count = " + count);
}
}
In the MyWidget class declaration above, I have a partial class named MyWidget. The first two lines
of code are partial method definitions. I have defined partial methods named MyWidgetStart and
MyWidgetEnd that each accept an int input parameter and return void. It is another rule that partial
methods must return void.
The next piece of code in the MyWidget class is the constructor. As you can see, I declare an int
named count and initialize it to 0. I then call the MyWidgetStart method, write a message to the console,
call the MyWidgetEnd method, and finally output the value of count to the console. Notice I am incrementing the value of count each time it is passed into a partial method. I am doing this to prove that
if no implementation of a partial method exists, its arguments are not even evaluated.
In Listing 2-16 I instantiate a MyWidget object.
Listing 2-16. Instantiating a MyWidget
MyWidget myWidget = new MyWidget();
Let’s take a look at the output of this example by pressing Ctrl+F5:
In the constructor of MyWidget.
count = 0
As you can see, even after the MyWidget constructor has incremented its count variable twice,
when it displays the value of count at the end of the constructor, it is still 0. This is because the code
for the evaluation of the arguments to the unimplemented partial methods is never emitted by the
compiler. No IL code was emitted for either of those two partial method calls.
Now let’s add an implementation for the two partial methods:
Rattz_789-3C02.fm Page 35 Tuesday, October 16, 2007 2:19 PM
36 CHAPTER 2 ■ C# 3.0 LANGUAGE ENHANCEMENTS FOR LINQ
Another Declaration for MyWidget but Containing Implementations for the Partial Methods
public partial class MyWidget
{
partial void MyWidgetStart(int count)
{
Console.WriteLine("In MyWidgetStart(count is {0})", count);
}
partial void MyWidgetEnd(int count)
{
Console.WriteLine("In MyWidgetEnd(count is {0})", count);
}
}
Now that you have added this declaration, run Listing 2-16 again and look at the results:
In MyWidgetStart(count is 1)
In the constructor of MyWidget.
In MyWidgetEnd(count is 2)
count = 2
As you can see, not only are the partial method implementations getting called, the arguments
passed are evaluated as well. You can see this because of the value of the count variable at the end of
the output.
What Is the Point of Partial Methods?
So you may be wondering, what is the point? Others have said, “This is similar to using inheritance
and virtual methods. Why corrupt the language with something similar?” To them I say “Take a
chill-pill Jill.” Partial methods are more efficient if you plan on allowing many potentially unimplemented hooks in the code. They allow code to be written with the intention of someone else extending it
via the partial class paradigm but without the degradation in performance if they choose not to.
The case in point for which partial methods were probably added is the code generated for LINQ
to SQL entity classes by the entity class generator tools. To make the generated entity classes more
usable, partial methods have been added to them. For example, each mapped property of a generated entity class has a partial method that is called before the property is changed and another partial
method that is called after the property is changed. This allows you to add another module declaring
the same entity class, implement these partial methods, and be notified every time a property is
about to be changed and after it is changed. How cool is that? And if you don’t do it, the code is no
bigger and no slower. Who wouldn’t want that?
The Rules
It has been all fun and games up to here, but unfortunately, there are some rules that apply to partial
methods. Here is a list:
• Partial methods must only be defined and implemented in partial classes
• Partial methods must specify the partial modifier
• Partial methods are private but must not specify the private modifier or a compiler error
will result
Rattz_789-3C02.fm Page 36 Tuesday, October 16, 2007 2:19 PM
CHAPTER 2 ■ C# 3.0 LANGUAGE ENHANCEMENTS FOR LINQ 37
• Partial methods must return void
• Partial methods may be unimplemented
• Parital methods may be static
• Partial methods may have arguments
These rules are not too bad. For what we gain in terms of flexibility in the generated entity
classes plus what we can do with them ourselves, I think C# has gained a nice feature.
Query Expressions
One of the conveniences that the C# language provides is the foreach statement. When you use foreach,
the compiler translates it into a loop with calls to methods such as GetEnumerator and MoveNext. The
simplicity the foreach statement provides for enumerating through arrays and collections has made
it very popular and often used.
One of the features of LINQ that seems to attract developers is the SQL-like syntax available for
LINQ queries. The first few LINQ examples in the first chapter of this book use this syntax. This syntax is
provided via the new C# 3.0 language enhancement known as query expressions. Query expressions
allow LINQ queries to be expressed in nearly SQL form, with just a few minor deviations.
To perform a LINQ query, it is not required to use query expressions. The alternative is to use
standard C# dot notation, calling methods on objects and classes. In many cases, I find using the
standard dot notation favorable for instructional purposes because I feel it is more demonstrative of
what is actually happening and when. There is no compiler translating what I write into the standard
dot notation equivalent. Therefore, many examples in this book do not use query expression syntax
but instead opt for the standard dot notation syntax. However, there is no disputing the allure of
query expression syntax. The familiarity it provides in formulating your first queries can be very
enticing indeed.
To get an idea of what the two different syntaxes look like, Listing 2-17 shows a query using the
standard dot notation syntax.
Listing 2-17. A Query Using the Standard Dot Notation Syntax
string[] names = {
"Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland",
"Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield",
"Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson",
"Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley",
"Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft",
"Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"};
IEnumerable<string> sequence = names
.Where(n => n.Length < 6)
.Select(n => n);
foreach (string name in sequence)
{
Console.WriteLine("{0}", name);
}
Listing 2-18 is the equivalent query using the query expression syntax:
Rattz_789-3C02.fm Page 37 Tuesday, October 16, 2007 2:19 PM
38 CHAPTER 2 ■ C# 3.0 LANGUAGE ENHANCEMENTS FOR LINQ
Listing 2-18. The Equivalent Query Using the Query Expression Syntax
string[] names = {
"Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland",
"Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield",
"Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson",
"Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley",
"Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft",
"Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"};
IEnumerable<string> sequence = from n in names
where n.Length < 6
select n;
foreach (string name in sequence)
{
Console.WriteLine("{0}", name);
}
The first thing you may notice about the query expression example is that unlike SQL, the from
statement precedes the select statement. One of the compelling reasons for this change is to narrow
the scope for IntelliSense. Without this inversion of the statements, if in the Visual Studio 2008 text
editor you typed select followed by a space, IntelliSense will have no idea what variables to display
in its drop-down list. The scope of possible variables at this point is not restricted in any way. By
specifying where the data is coming from first, IntelliSense has the scope of what variables to offer
you for selection. Both of these examples provide the same results:
Adams
Bush
Ford
Grant
Hayes
Nixon
Polk
Taft
Tyler
It is important to note that the query expression syntax only translates the most common query
operators: Where, Select, SelectMany, Join, GroupJoin, GroupBy, OrderBy, ThenBy, OrderByDescending,
and ThenByDescending.
Query Expression Grammar
Your query expressions must adhere to the following rules:
1. A query expression must begin with a from clause.
2. The remainder of the query expression may then contain zero or more from, let, or where
clauses. A from clause is a generator that declares one or more enumerator variables enumerating over a sequence or a join of multiple sequences. A let clause introduces a variable
and assigns a value to it. A where clause filters elements from the sequence or join of multiple
sequences into the output sequence.
Rattz_789-3C02.fm Page 38 Tuesday, October 16, 2007 2:19 PM
CHAPTER 2 ■ C# 3.0 LANGUAGE ENHANCEMENTS FOR LINQ 39
3. The remainder of the query expression may then be followed by an orderby clause which
contains one or more ordering fields with optional ordering direction. Direction is either
ascending or descending.
4. The remainder of the query expression must then be followed by a select or group clause.
5. The remainder of the query expression may then be followed by an optional continuation
clause. A continuation clause is either the into clause, zero or more join clauses, or another
repeating sequence of these numbered elements beginning with the clauses in No. 2. An
into clause directs the query results into an imaginary output sequence, which functions as
a from clause for a subsequent query expression beginning with the clauses in No. 2.
For a more technical yet less wordy description of the query expression syntax, use the following
grammar diagram provided by Microsoft in the MSDN LINQ documentation:
query-expression:
from-clause query-body
from-clause:
from typeopt identifier in expression join-clausesopt
join-clauses:
join-clause
join-clauses join-clause
join-clause:
join typeopt identifier in expression on expression equals
expression
join typeopt identifier in expression on expression equals
expression into identifier
query-body:
from-let-where-clausesopt orderby-clauseopt select-or-group-clause
query-continuationopt
from-let-where-clauses:
from-let-where-clause
from-let-where-clauses from-let-where-clause
from-let-where-clause:
from-clause
let-clause
where-clause
let-clause:
let identifier = expression
where-clause:
where boolean-expression
orderby-clause:
orderby orderings
Rattz_789-3C02.fm Page 39 Tuesday, October 16, 2007 2:19 PM