I used to be connections.
A super fan of the series, James Burke from the Discovery Channel
Host the program. The basic assumption is: how seemingly unrelated discoveries affect other discoveries, which ultimately facilitate modern life. The implication is that if you want to make progress, any progress will not be alone.
Site obtained. The same is true for language integration query (LINQ.
Simplified
Specifically, LINQ supports a series of language extensions for querying data in a type-safe manner. It will be in the next version of Visual Studio codenamed "orcas"
Publish. The data to be queried can be in the form of XML (LINQ to XML), Database (ADO. Net with LINQ enabled, including
SQL, LINQ to dataset, and LINQ to entities) and objects (LINQ to objects. The LINQ architecture is as follows:Figure 1.
Figure 1 LINQ architecture (click the image to get a small view)
Figure 1 LINQ architecture (click the image to get a larger view)
Let's take a lookCode. In the "orcas" Version C # to be released, the LINQ query may be as follows:
VaR overdrawnquery = from account in db. Accounts
Where account. Balance <0
Select New {account. Name, account. Address };
When you use foreach to traverse the results of this query, each element returned will contain the name and address of an account whose balance is less than 0.
Slave
The preceding example shows that the syntax is similar to SQL. Several years ago, Anders hejlsberg (chief designer of C #) and Peter Golde
I have considered extending C # to better integrate data query. Peter was then C # compiler development director and was studying the possibility of extending the C # compiler, especially supporting verifiable
SQL and other domain-specific language syntax-specific add-ons. On the other hand, Anders is imagining more in-depth and specific integration. He was thinking about a set of "sequence operators" that could be implemented
Any set of ienumerable and iqueryable remote query run. In the end, the idea of sequence operators is most supported, and
Anders sent thinkweek to Bill Gates in early 2004.
I submitted a document about this idea. The feedback fully affirmed this. At the initial stage of design, the simple query syntax is as follows:
Sequence <customer> locals = MERs. Where (zipcode = 98112 );
In this example, sequence is
The alias of ienumerable <t>. The word "where" is a special operator that the compiler can understand. The implementation of the where operator is acceptable.
Ordinary C #
Static method. This idea aims to give the editor special knowledge about operators. This will allow the compiler to call the static method correctly and create code to associate the delegate with the expression.
Assume that the preceding example is the ideal query syntax of C. What does the query look like in C #2.0 without any language extensions?
Ienumerable <customer> locals = enumerableextensions. Where (MERS MERs,
Delegate (customer C)
{
Return C. zipcode = 98112;
});
This code is incredibly lengthy and, worse, requires
The related filter (zipcode =
98112 ). This is just a simple example. Imagine how difficult it is to read the code if several filters and projections are used. The lengthy syntax is rooted in the syntax required by the anonymous method. In the ideal query
Except for the expressions to be calculated, the expressions do not require any operation. Then, the compiler will try to deduce the context. For example, zipcode actually references
Zipcode. How can this problem be solved? Hard coding of knowledge about specific operators into a language does not satisfy the language design team, so they began to seek alternative syntaxes for anonymous methods. They require the syntax to be
It is extremely concise, but does not require more knowledge than the compiler currently required for anonymous methods. Finally, they invented the lambda expression.
Lambda
Expressions are a language function and are similar to anonymous methods in many aspects. In fact, if Lambda
The expression is first introduced into the language, so there is no need for anonymous methods. The basic concept here is that code can be considered as data. In C #1.0
Generally, strings, integers, and reference types can be passed to the method so that the method can operate on those values. Anonymous method and Lambda
The expression extends the range of values to include code blocks. This concept is common in functional programming.
We can use the above example and use a Lambda expression to replace the anonymous method:
Ienumerable <customer> locals =
Enumerableextensions. Where (MERS MERs, c => C. zipcode = 91822 );
There are several notes. For beginners,
Lambda expressions are concise and concise for many reasons. First, the constructor is not introduced using the delegate keyword. Instead, it is a new operator.
=>, Notifies the compiler that this is not a regular expression. Secondly, the customer type is inferred from usage. In this example, the signature of the where method is as follows:
Public static ienumerable <t> where <t> (
Ienumerable <t> items, func <t, bool> predicate)
The compiler can infer that "C" refers to the customer, because
The first parameter of the where method is ienumerable <customer>, so t must be
Customer. With this knowledge, the compiler can also verify that customer has a zipcode
Member. Finally, no specified keyword is returned. In syntax form, the returned Members are omitted, but this is only for syntax convenience. The result of the expression is still treated as the return value.
Like Anonymous methods, lambda expressions also support variable capturing. For example, for a method that contains a Lambda expression within the lambda expression body, you can reference its parameters or local variables:
Public ienumerable <customer> localcusts (
Ienumerable <customer> MERs, int zipcode)
{
Return enumerableextensions. Where (MERS MERs,
C => C. zipcode = zipcode );
}
Finally, lambda expressions support more lengthy syntax, allowing you to explicitly specify the type and execute multiple statements. For example:
Return enumerableextensions. Where (MERS MERs,
(Customer C) =>{ int zip = zipcode; return C. zipcode = zip ;});
The good news is thatArticleThe ideal syntax proposed in is a huge step forward, and we can achieve this by using a language feature that normally works beyond the query operator. Let's take a look at our current stage again:
Ienumerable <customer> locals =
Enumerableextensions. Where (MERS MERs, c => C. zipcode = 91822 );
There is an obvious problem here. Currently, the customer must understand this enumerableextensions class, rather than considering the operations that can be performed on the customer. In addition, in the case of multiple operators, users must reverse their thinking to write the correct syntax. For example:
Ienumerable <string> locals =
Enumerableextensions. Select (
Enumerableextensions. Where (customers, c => C. zipcode = 91822 ),
C => C. Name );
Note that select is an external method, although it runs on the basis of the where method result. The ideal syntax should be similar to the following code:
Sequence <customer> locals =
Customers. Where (zipcode = 98112). Select (name );
Therefore, is it possible to use the functions of another language to further approach implementing the ideal syntax?
Extension Method
End
It turns out that a better syntax will appear in the form of a language function called an extension method. The extension method is basically a static method that can be called through the instance syntax. The root cause of the above query problem is that we try
Ienumerable <t> Add method. However, if we want to add operators such as where and select
All existing and future implementers must implement those methods. Although most of those implementations are the same. In C #
The only way to share "interface implementation" in is to use the static method, which is a successful method for processing the previously used enumerableextensions class.
Suppose we write the where method as an extension method instead. You can rewrite the query as follows:
Ienumerable <customer> locals =
Customers. Where (C => C. zipcode = 91822 );
This syntax is almost perfect for this simple query. But what is the true meaning of writing the where method as an extension method? It is actually very simple. Basically, because the signature of the static method is changed, the "this" modifier is added to the first parameter:
Public static ienumerable <t> where <t> (
This ienumerable <t> items, func <t, bool> predicate)
In addition, the method must be declared in the static class. Static class
Is a class that can only contain static members and is represented by a static modifier in the class declaration. This is all about its meaning. This statement instructs the compiler to allow
For the ienumerable <t> type, use the same syntax as the instance method to call where. However, you must be able to access where from the current scope
Method. When the include type is in the scope, the method is also in the scope. Therefore, you can use using
The instruction introduces the extension method into the scope. (For more information, see "extension methods" on the sidebar ".)
Extension Method
Obviously, expansion
Methods help to simplify our query examples, but are these methods a widely used language function? It turns out that the extension method has multiple purposes. One of the most common purposes is to provide shared interface implementation. For example, assume that you have the following interfaces:
Interface idog
{
// Barks for 2 seconds
Void bark ();
Void bark (INT seconds );
}
This interface requires that each Implementer should write an implementation that applies to two kinds of overloading. With "orcas" C #, the interface becomes very simple:
Interface idog
{
Void bark (INT seconds );
}
The extension method can be added to another class:
Static class dogextensions
{
// Barks for 2 seconds
Public static void bark (this idog dog)
{
Dog. Bark (2 );
}
}
The interface implementer now only needs to implement a single method, but the interface client can freely call any overload.
Close [x]
We now have a very close-to-ideal syntax for writing filter clauses, but is "orcas" C # limited to this? Not totally. Let's make a little extension to the example. Compared with the entire customer object, we only project the customer name. As I mentioned earlier, the ideal syntax should be in the following format:
Sequence <string> locals =
Customers. Where (zipcode = 98112). Select (name );
This code can be rewritten as follows:
Ienumerable <string> locals =
Customers. Where (C => C. zipcode = 91822). Select (C => C. Name );
Note that the return type of this query is different. It is ienumerable <string> rather than ienumerable <customer>. This is because only the customer name is returned from the SELECT statement.
This method is indeed effective when projection is just a single field. However, assume that we not only need to return the customer name, but also the customer address. The ideal syntax should be as follows:
Locals = customers. Where (zipcode = 98112). Select (name, address );
Anonymous type
If we want to continue using our existing syntax to return names and addresses, we will soon be faced with problems, that is, there is no type that only contains name and address. Although we can still write this query, this type must be introduced:
Class customertuple
{
Public string name;
Public String address;
Public customertuple (string name, string address)
{
This. Name = Name;
This. Address = address;
}
}
Then we can use this type, that is, customertuple, to generate the query results.
Ienumerable <customertuple> locals =
Customers. Where (C => C. zipcode = 91822)
. Select (C => New customertuple (C. Name, C. Address ));
It is indeed like many sample generations used to project a subset of fields.
. It is often unclear how to name this type. Is customertuple a good name? If name and age are projected
What should I do? It can also be called
Customertuple. Therefore, the problem is that we have sample code and it seems that we cannot find any proper name for the type we created. In addition, many different types may be required.
Managing these types quickly becomes a tricky issue.
This is the problem to be solved by the anonymous type. This function allows you to create a structural type without specifying a name. If we re-compile the above query using the anonymous type, the Code is as follows:
Locals = customers. Where (C => C. zipcode = 91822)
. Select (C => New {C. Name, C. Address });
This Code implicitly creates a type with the name and address fields:
Class
{
Public string name;
Public String address;
}
This type cannot be referenced by name because it has no name. When creating an anonymous type, you can explicitly declare the field name. For example, if the field being created is derived from a complex expression, or you do not need a name, you can change the name:
Locals = customers. Where (C => C. zipcode = 91822)
. Select (C => New {fullname = C. firstname + "+ C. lastname,
Homeaddress = C. Address });
In this case, the generated type has fields named fullname and homeaddress.
In this way, we have taken another step towards the ideal world, but there is still a problem. You will find that the type of the local variable is strategically omitted in any location where the anonymous type is used. Obviously, we cannot declare anonymous names. How do we use them?
Implicitly typed Partial Variables
Another language is called implicit typed local variables (or var for short), which instruct the compiler to deduce the type of local variables. For example:
VaR integer = 1;
In this example, the integer type is int. Be sure to understand that this is still a strong type. In dynamic languages, the integer type can be changed later. To illustrate this, the following code will not be compiled successfully:
VaR integer = 1;
Integer = "hello ";
C # The Compiler reports errors in the second line, indicating that the string cannot be implicitly converted to int.
In the preceding query example, we can write a complete value assignment, as shown below:
VaR locals =
MERs
. Where (C => C. zipcode = 91822)
. Select (C => New {fullname = C. firstname + "+ C. lastname,
Homeaddress = C. Address });
The local variable type eventually becomes ienumerable <?>, "?" Is a type name that cannot be written (because it is anonymous ).
Implicitly typed local variables are only local variables in the method. They cannot exceed the boundary of methods, attributes, indexers, or other blocks because the type cannot be explicitly declared and "Var" is invalid for fields or parameter types.
It turns out that the implicitly typed local variables are very convenient outside the query environment. For example, it helps simplify complex General instantiation:
VaR customerlistlookup = new dictionary <string, list <customer> ();
Now we have made good progress in our queries. We are close to the ideal syntax, and we achieved it using the universal language function.
Interestingly, we found that as more and more people use this syntax, there is often a need to allow projection to go beyond the boundary of the method. As we have seen before, this is possible. You only need to call the constructor of the object within the SELECT statement to build the object. However, what happens if the constructor is not used to accurately accept the values you need to set?
Object Initial Value
To solve this problem, the forthcoming "orcas" version provides a C # language function called the initial value of an object. Object initial values can be assigned to multiple attributes or fields in a single expression. For example, the common mode for creating an object is:
Customer customer = new customer ();
Customer. Name = "Roger ";
Customer. Address = "1 Wilco way ";
At this time, the customer does not have constructors that can accept the name and address. However, there are two attributes: Name and address. You can set them after creating an instance. Object initial values allow the following syntax to create the same results:
Customer customer = new customer ()
{Name = "Roger", address = "1 Wilco way "};
In our previous customertuple example, we created the customertuple class by calling its constructor. We can also get the same result through the initial object value:
VaR locals =
MERs
. Where (C => C. zipcode = 91822)
. Select (C =>
New customertuple {name = C. Name, address = C. Address });
Note that the brackets of the constructor can be omitted from the object's initial values. In addition, fields and configurable attributes can be assigned values within the subject of the object's initial value.
We now have a concise syntax for creating a query in C. However, we also have a scalable way to add new operators (distinct, orderby, sum, etc.) by using the extension method and a group of language functions that are very useful ).
Language
The language design team now has several prototype for feedback. Therefore, we work with many rich C # and SQL
Experienced participants organized a usability study. Almost all feedback is positive, but something is neglected. Specifically, it is difficult for developers to apply their SQL
Knowledge, because we believe that the ideal syntax is not very consistent with the specialized technologies they are good.
Query expression
Therefore, the language design team designed a syntax that is more similar to SQL, called a query expression. For example, the query expression for our example can be as follows:
VaR locals = from C in mers MERs
Where C. zipcode = 91822
Select New {fullname = C. firstname + "+
C. lastname, homeaddress = C. Address };
The query expression is based on the functions of the preceding language. In terms of syntax, they are fully converted to the basic syntax we have seen. For example, the preceding query can be directly converted:
VaR locals =
MERs
. Where (C => C. zipcode = 91822)
. Select (C => New {fullname = C. firstname + "+ C. lastname,
Homeaddress = C. Address });
The query expression supports many different "clauses", such
From, where, select, orderby, group by, let
Join. These clauses are first converted to equal operator calls, which are then implemented through extension methods. If the query syntax does not support clauses of necessary operators
It is easy to combine the two. For example:
VaR locals = (from C in mers MERs
Where C. zipcode = 91822
Select New {fullname = C. firstname + "+
C. lastname, homeaddress = C. Address })
. Count ();
In this example, the number of customers living in the 91822 zip code area is queried.
Connect
Through this method, we have managed to reach the goal at the beginning at the end (I am always very satisfied with this ). C # Of the next version #
After several years of development, I tried many new language functions to finally reach the realm of the original syntax proposed in the winter of 2004. The query expression is added to C #
Other language functions of the version to be released are based on, and many query conditions are easier for developers with SQL background to read and understand.
Author profile:
Anson HortonI have been a project manager at Microsoft for nearly six years. From C #
Since the establishment of the team, he has always been a member of the C ++ team. He used to design C # language and compiler, C # project system, and C # IDE.
(Intelliisense) and C # expression calculator and debugger. Anson owns a blog in blogs.msdn.com/ansonh and rarely updates the content.
Original: http://msdn.microsoft.com/msdnmag/issues/07/06/CSharp30/Default.aspx? Loc = ZH