C # Review notes (4)--c#3: Innovative Ways to write code (query expressions and LINQ to Object (above))

Source: Internet
Author: User


query expressions and LINQ to Object (top)


The contents of this chapter:


    • Streaming data and deferred execution sequences
    • Standard query operators and query expression conversions
    • Range variables and transparent identifiers
    • Projection, filtering, and sorting
    • Joins and Groups
    • Select the syntax to use
Introduction to Concepts in LINQ


Sequence






You should certainly be familiar with the concept of sequence: it is encapsulated by the IEnumerable and ienumerable< t> interfaces, and the sequence is like the carousel of data items-you can only get them one at a time until you no longer want to get the data, or there is no data in the sequence.



The biggest difference between a sequence and other data collection structures is that you don't usually know how many items are in the sequence--or you can't access any of them, only the current one. Lists and arrays can also be used as sequences, because list< t> implements the ienumerable< t>--, which in turn is not always feasible. For example, you cannot have an infinite array or list.



The sequence is the basis of LINQ. The first query expression is always for a sequence, and as the operation progresses, the sequence may be converted or chained together with more sequences.



Take a look at the following:


 
 
var adaultNames = from person in People
                where person.Age > 18
                select person.Name;


The following is a diagram that splits this operation into steps:






Before decomposing this step, it is important to tell why the status of the sequence in LINQ is so significant: this is because they are the basis of the flow model of data processing, allowing us to acquire and process data only when needed.



Each of the arrows represents a sequence--described on the left, the sample data on the right. Each box represents a step in the query expression. Initially, we have the entire family member (represented by the person object). Then, after filtering, the sequence contains only the adult (or the Person object). The final result contains the names of these adults in string form. Each step is to get a sequence that applies actions on the sequence to generate a new sequence. The result is not the string "Holly" and "Jon"--but ienumerable<string>, so that when the element is fetched from inside one after another, "Holly" is generated first, followed by "Jon".



Take a look at what's behind it: the expression that was created first, the expression created is simply a representation of the query in memory, which is represented using a delegate. Only when the first element of the result set (adaultnames) is accessed does the entire "wheel" roll forward (the iterator is likened to the wheel). This feature of LINQ is called deferred execution. When the first element of the final result is accessed, the Select transformation calls the where transformation for its first element. The where transformation accesses the first element in the list, checks whether the predicate matches (in this case, matches), and returns the element to select. Finally, the name is extracted in turn to return as the result. Of course, the various parameters involved must perform a controllability check, which is important to keep in mind if you are implementing your own LINQ operators.



Shows how the query expression runs in several stages when you invoke each item in the result sequence with foreach






This is the characteristic of streaming, although it involves a few stages, however, this way of using the flow of data processing is very efficient and flexible. In particular, no matter how many data sources you have, you just need to know one of them at a certain point in time.



There is a buffer compared to streaming, because you sometimes have to load all the elements in the sequence into memory for calculations, such as reverse operations. He needs to extract all the available data from the sequence to return the last element as the first element. This, of course, can have a significant performance impact on the execution of the entire expression in efficiency.



Regardless of whether they are stream or buffered, they are deferred operations, where the data is actually transferred only when the first element in the result set is enumerated, in contrast to immediate execution-some transformations are executed immediately once they are called. In General, operations that return another sequence (typically ienumerable<t> and iqueryable<t>) perform deferred operations, and operations that return a single value are executed immediately.



LINQ standard Query operators


The standard query operator for LINQ is a collection of transformations with a clear meaning. Standard query operators have a common meaning, but under different LINQ providers, the semantics are different, and some LINQ providers may load all the data, such as Web services, when they get the first element. This prompts us to consider what data source is used when writing query operations.



Some of the standard query operators supported by C#3 are built into the language through query expressions.



The extension Pack for LINQ to Object MORELINQ can be downloaded through NuGet. and reactive Extensions.


Instance data for this chapter


To get started with this chapter you need to have a sample data that is available on the C # in depth website, but you can find this sample data from Baidu. It's not going to come out here, it's too much space.


Simple start: Select an element
static void Main(string[] args)
        {
           // ApplicationChooser.Run(typeof(Program), args);
            var query = from user in SampleData.AllUsers
                select user;
            foreach (User user in query)
            {
                Console.WriteLine(user);
            }

            Console.ReadKey();
        }
Note: Sampledata.allusers This is the sample data
The query expression is the part that is marked in bold.
This example is useless, and we can use Sampledata.allusers directly in foreach. ———— We will use this example to elicit two concepts: ① translates ② range variables. First look at the translation : The compiler translates the query expression into normal C # code, which is the basis for supporting c#3 query expressions. When translating, he will not check for errors, nor will it be effective, that is, mechanical translation. The above example is translated as follows:
var query = SampleData.AllUsers.Select (user = user);


You can see that the goal of the translation is a method call. The C#3 compiler will first translate the query expression into this form before further cheap code. In particular, it does not check whether to use Enumerable.select or list<t> Select, which will be further determined by the compiler after the translation. Translation focuses only on the subsequent compiler's ability to compile the translated code correctly--it is only responsible for this link. The important thing is that LMABDA can be converted to a delegate and an expression tree, which is the basis of what the compiler does next. Later, when I introduce the signature of some methods called by the compiler, remember to make only one invocation in LINQ to Objects--at any time, the parameters (most of them) are delegate types, the compiler will use a lambda expression as an argument, and try to find a method with the appropriate signature. It must also be remembered that, after translation execution, regardless of the normal variables in the lambda expression (such as local variables inside the method), it will be converted to capture variables in the way we saw in the previous section. This is just the behavior of ordinary lambda expressions--but unless you understand which variables will be captured, it's easy to get confused by the query results.


How query expressions are implemented
 
class Dummy<T>
    {
        public Dummy<T> Select<T>(Func<T, bool> predicate)
        {
            Console.WriteLine("Select called");
            return new Dummy<T>();
        }
    }

    static class Extenssion
    {
        public static Dummy<T> Where<T>(this Dummy<T> dummy, Func<T, bool> predicate)
        {
            Console.WriteLine("Where called");
            return dummy;
        }
    }


static void Main (string[] args)
{
var Source = new Dummy<string> ();
var query = from dummy in source
where dummy. ToString () = = "ignored"
Select "Anything";



Console.readkey ();
}



The code above confirms what we said at the beginning, that's how translation works, and we're just going to define some instance methods and extension methods on a certain type, and then we can use query expressions to write queries that don't care if you're using some extension methods based on IEnumerable. So, he will be turned to the following code:


var " ignored " " anything ");


Note that the "anything" is used in the query expression select instead of dummy this is because the special case of select Dummy is translated and deleted. My understanding is that adding and adding is no use, no effect.



Note that the dummy class does not actually implement IENUMERABLE<T>, which shows that translation does not depend on specific types but on specific method names and parameters, which is also a type of duck, and many parts of C # are duck types, For example, the root cause of the enumerator's ability to enumerate is to find out if the type contains a GetEnumerator method, as well as async and await, which are explained later.



And then look at another concept: range variables






Or the query expression above, the context keyword is easy to explain-they tell the compiler exactly what we want to do with the data. Similarly, a data source expression is just a normal C # expression-a property in this case, but it can also be a simple method call or variable.



The more difficult to understand here is the scope variable declaration and the projection expression. Range variables are not like other kinds of variables. In some ways, it is not a variable at all. They can only be used in query expressions and actually represent contextual information that is passed from one expression to another. They represent an element in a particular sequence, and they are used in compiler translation to easily translate other expressions into lambda expressions.



We already know that the initial query expression will be converted to the following form:


SampleData.AllUsers.Select (user = user)


The left side of the lambda expression is the range variable, and the right side is the logic of the SELECT clause, and the process of translation is simple.



In a more complex translation process, such as SampleData.AllUsers.Select (user = user). Name) is also dependent on C#3 's more sophisticated type inference, which considers all type parameters as a whole and can infer another type parameter based on one type parameter, which is why the LMABDA expression allows implicit typing. Everything is attributed to C#3 's more powerful and sophisticated type inference. (This is actually described in the previous chapters).



So far, we are really a strongly typed collection that uses the query operator, but there are also some weakly-typed collections, such as ArrayList and object[], when the cast and oftype operators come in handy.


 
Static void Main(string[] args)
         {

             ArrayList list = new ArrayList() { "first", "second", "third" };
             IEnumerable<string> strings = list.Cast<string>();
             Foreach (string item in strings)
             {
                 Console.WriteLine(item);//output "first", "second", "third" in turn
             }
             ArrayList anotherList = new ArrayList()
            {
                1, "first", 3, "fourth"
            };
             IEnumerable<int> ints = anotherList.OfType<int>();
             Foreach (int item in ints)
             {
                 Console.WriteLine(item);//Output output 1,3
             }
             Console.ReadKey();
         }


When you convert this weakly typed collection to a strongly typed collection, the cast and oftype mechanisms differ, and case tries to convert each element, and when it encounters an unsupported type, it will get an error, but pay attention to the timing of the error: only after the output 1, the error is Because both cast and OfType stream the sequence. And OfType will try to convert each element, skipping the unqualified elements.



Cast and OfType only allow consistency, unboxing, and boxing conversions. The conversion between list<int> and list<short> will fail--cast will report an exception, OfType will not.



In a query expression, the type of the declared range variable that is displayed and the execution of the cast are bound together: if the type of the declared range variable is displayed in a set of weakly types:


 
static void Main(string[] args)
        {

            ArrayList list = new ArrayList() { "first", "second", "third" }; var query = from string oneString in list select oneString.Substring(0, 3); foreach (string item in query)
            {
                Console.WriteLine(item);
            }
                  Console.ReadKey();
        }


After this is translated, it will be programmed like this.


  var anotherquery = list. cast<string> (). Select (Li = li. Substring (03));


Without this type conversion (CAST) we simply cannot invoke select ———— because select is only for ienumerable<t> and cannot be used for IEnumerable.



Of course, in addition to using explicitly declared scope type variables in a weakly typed collection, this is also used in strongly typed types. For example,,list< interface > You might want to use an explicit type for the "Interface Implementation" declaration of the scope type, because you know that the List is loaded with "interface implementation" instead of "interface."



Next, we describe some important concepts:


    • LINQ is based on a data sequence and is streamed wherever possible.
    • Creating a query does not immediately execute it: most operations delay execution.
    • The query expression for C#3 includes a preprocessing phase that converts an expression to plain C # code, and then uses these general rules, such as type inference, overloading, lambda expressions, to properly compile the converted code.
    • What variables are declared in a query expression: they are just scope variables through which you can consistently reference data within a query expression.

Filtering and sorting sequences

where


This operator, which introduces a lot of variable filtering functions, has uncovered some secrets, such as streaming. The compiler translates this clause into a where method call with a lambda expression, which uses the appropriate range variable as the parameter of the lambda expression, and filters the expression as the body. The filter expression is treated as a predicate that goes into each element of the data flow, and only the element that returns true can appear in the result sequence. Using multiple WHERE clauses results in a multiple-link where call--only the element that satisfies all the predicates can enter the result sequence.


 static void Main(string[] args)
        {
            User tim = SampleData.Users.TesterTim; var query = from defect in SampleData.AllDefects where defect.Status != Status.Closed where defect.AssignedTo == tim select defect.Summary; foreach (string item in query)
            {
                Console.WriteLine(item);
            }
            Console.ReadKey();
        }


The above two-where query expression will be translated as follows:



  
 var anotherQurty = SampleData.AllDefects
                .Where(de => de.Status != Status.Closed)
                .Where(de => de.AssignedTo == tim)
                .Select(de => de.Summary);


We can, of course, merge the two where into one, which may improve some performance, but also consider readability.


backspace for query expressions


The backspace means that if a select operator does nothing but returns the same sequence of the given sequence, the associated invocation of select is removed in the translated code:


 
var myFirstQuery = from def in SampleData.AllDefects select def;

The above code after the translation of the compiler will deliberately add a select operator in the back, do not think I said, when I finished all the statement, you understand:


 
var mySecondQuery = SampleData.AllDefects.Select(de => de);


There is a fundamental difference between adding a select and not increasing it, and the Select method expresses the meaning of returning a new sequence, meaning that we do not perform any crud operations on the original data, just return a new data source, and operate on the data source. Does not have any effect on the original data.




User tim = SampleData.Users.TesterTim;
            var query = from defect in SampleData.AllDefects
                where defect.Status != Status.Closed
                where defect.AssignedTo == tim
                select defect;



Now we don't need the call of SELECT, the translated code is as follows:


varAnotherquery = SampleData.AllDefects.Where (Defec = Defec. Status! =status.closed). Where (Defec= = Defec. AssignedTo = = Tim); using the ORDER BY clause
 
 
...
 User tim = SampleData.Users.TesterTim;
            var query = from defect in SampleData.AllDefects
                        where defect.Status != Status.Closed
                        where defect.AssignedTo == tim
                        orderby defect.Severity descending
                        select defect;
....


If you download the sample code for this chapter, the following results are returned:


Showstopper-Webcam makes me look bald
Major-Subtitles only work in Welsh
Major-Play button points the wrong way
Minor-Network is saturated when playing WAV file
Trivial-Installation is slow


You can see that two major have been returned, but how are the two major sorted? We further retrofit the code:


 
....
 User tim = SampleData.Users.TesterTim;
            var query = from defect in SampleData.AllDefects
                        where defect.Status != Status.Closed
                        where defect.AssignedTo == tim
                        orderby defect.Severity descending,defect.LastModified
                        select defect;
....


We are under defect. Severity descending are sorted and then based on defect. LastModified the class sort.



This statement is translated into the following code:


 
....
var anotherQeury = SampleData.AllDefects
                .Where(de => de.Status != Status.Closed)
                .Where(de => de.AssignedTo == tim)
                .OrderByDescending(de => de.Severity)
                .ThenBy(de => de.LastModified);
....


You can see the "defect. Severity Descending,defect. LastModified "This sentence is translated into a ... The form of ThenBy. At the same time select is removed, the reason above has a solution.



The following summarizes the principle of the ORDER BY clause:


    • They are basically the context keyword, followed by one or more collations.
    • A collation is an expression (which can be used with a range variable), followed by the ascending or descending keyword, and its meaning is obvious (the default rule is ascending.) )
    • The translation of the primary collation is called by an order or orderbydescending, while the other sub-collations are converted by calling ThenBy or thenbydescending, as we see in our example. The difference between the order and the ThenBy is very simple: it assumes that it determines the collation, whereas the ThenBy can be interpreted as an adjunct to one or more of the previous ordering rules.
    • You can use multiple order-by clauses, but only the last one wins, which means that the previous ones are useless.
    • Applying collations requires that all data be loaded (at least for LINQ to Objecs)--for example, you can't sort an infinite sequence. The reason for this is obvious, for example, before you see all the elements, you don't know if something you see is at the beginning of the result sequence.





C # Review notes (4)--c#3: Innovative Ways to write code (query expressions and LINQ to Object (above))


Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.