C ා basic knowledge series-7 LINQ details

Time:2020-6-6

preface

In the previous article, I briefly introduced LINQ’s entry-level usage. This article tries to explain some more in-depth usage methods. Unlike the previous article, I will first introduce the support methods in LINQ, and then write them in the form of method chain and SQL like, guided by the actual requirements.

Introduction to pre concept

  1. Predicate<T>Predicate, assertion, equivalent toFunc<T,bool>The expression that returns bool
  2. Expression<TDelegate>Expression tree, this class is very important, but we will elaborate here, and we will talk about a special generic type:Expression<Func<T,bool>>This is very important in queries of some data sources. It represents a special expression in lambda expression, that is, there is no brace andreturnKeyword.

Let’s prepare two classes first

  1. Student / student:

    /// <summary>
    ///Students
    /// </summary>
    public class Student
    {
        /// <summary>
        ///Student number
        /// </summary>
        public int StudentId { get; set; }
        /// <summary>
        ///Name
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        ///Class
        /// </summary>
        public string Class { get; set; }
        /// <summary>
        ///Age
        /// </summary>
        public int Age { get; set; }
    }
  2. Subject / subject:

    /// <summary>
    ///Subject
    /// </summary>
    public class Subject
    {
        /// <summary>
        ///Name
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        ///Grade
        /// </summary>
        public string Grade { get; set; }
        /// <summary>
        ///Student number
        /// </summary>
        public int StudentId { get; set; }
        /// <summary>
        ///Achievements
        /// </summary>
        public int Score { get; set; }
    }

Subject and student are associated one by one through student number field. In actual work, data table may be designed as this.

First virtual two data sources:IEnumerable<Student> studentsandIEnumerable<Subject> subjects。 First, ignore the actual sources of these two data sources, because there are many kinds of data sources in the development process, such as the results of database query, the results of remote interface return, the results of file reading, etc. But in the end, it will beIEnumerable<T>Object that implements the class.

Introduction to common methods

Where to filter data and find qualified results

Method declaration for where:

public IEnumerable<TSource> Where<TSource> (this IEnumerable<TSource> source, Func<TSource,bool> predicate)

It can be seen that the data type will not be converted. Filter through the given lambda expression or a method to get the element that returns true.

Example:

//Get students older than 10 but not older than 12
List<Student> results = students.Where(t=>t.Age >10 && t.Age<= 12).ToList();

Note that the data will not be actually queried until tolist is called.

Group, group according to specified content

There are many group method declarations:

The most commonly used one is:

public static IEnumerable<System.Linq.IGrouping<TKey,TSource>> GroupBy<TSource,TKey> (this IEnumerable<TSource> source, Func<TSource,TKey> keySelector);

Example:

//Group students by class
List<IGrouping<string,Student>> list = students.GroupBy(p => p.Class).ToList();

Orderby / orderbydescending

They are a pair of methods, one is ascending and the other is descending. Their declarations are the same:

Commonly used:

public static System.Linq.IOrderedEnumerable<TSource> OrderBy<TSource,TKey> (this IEnumerable<TSource> source, Func<TSource,TKey> keySelector);

Example:

//In ascending order of age:
List<Student> results = students.OrderBy(p => p.Age).ToList();
//In descending order of age:
List<Student> results = students.OrderByDescending(p => p.Age).ToList();

First / last get the first / last data source

There are two common overloaded declarations for this set of methods:

First:

//Get the first one directly
public static TSource First<TSource> (this IEnumerable<TSource> source);
//Get the first one that meets the condition
public static TSource First<TSource> (this IEnumerable<TSource> source, Func<TSource,bool> predicate);

Last:

//Get the last one directly
public static TSource Last<TSource> (this IEnumerable<TSource> source);
//Get the last element that meets the condition
public static TSource Last<TSource> (this IEnumerable<TSource> source, Func<TSource,bool> predicate);

Example:

Student student =  students.First (); // equivalent to students [0];
Student student =  students.First (P = > p.class = = "class 1"); // get the first class in the data source

Student student =  students.Last (); // last student
Student student =  students.Last (P = > p.class = = "Class 3"); // get the last class 3 in the data source

be careful:

  • Using last in some data sources will report an error, because for some pipe type data sources or asynchronous data sources, the program cannot confirm the location of the last element, so it will report an error. Solution: first use orderby to sort the data source once, so that the results are in the opposite order, and then use first to get
  • When the data source is empty, or there is no element satisfying the condition, calling this set of methods will report an error. Solution: call firstordefault / lastordefault, these two groups of methods will return a default value when the result cannot be queried.

Whether any / all exists / satisfies

Any: whether there is element satisfying the condition

There are two versions, but the meaning may be different:

Public static bool any < tSource > (this IEnumerable < tSource > source); // whether there is data in the data source
//================
//Whether there is data meeting the conditions
public static bool Any<TSource> (this IEnumerable<TSource> source, Func<TSource,bool> predicate);

All: whether all conditions are met:

public static bool Any<TSource> (this IEnumerable<TSource> source, Func<TSource,bool> predicate);

Example:

//Are there any students
bool isAny =  students.Any();
//Do you have class five students
bool isFive =  students.Any (P = > p.class = = "class 5");
//Are all students at least 9 years old
bool isAll = students.All(p=>p.Age >= 9);

Skip omits several elements

There are three derivation methods for skip:

First: skip itself: skip a few elements and return the rest

public static IEnumerable<TSource> Skip<TSource> (this IEnumerable<TSource> source, int count);

The second one is skiplast, which skips several elements from the tail and returns the rest

public static IEnumerable<TSource> SkipLast<TSource> (this IEnumerable<TSource> source, int count);

Third: skipwhile, skip the elements that meet the conditions and return the remaining elements

public static IEnumerable<TSource> SkipWhile<TSource> (this IEnumerable<TSource> source, Func<TSource,bool> predicate);

Example:

//Don't keep the top 10 students
List<Student> results = students.Skip(10).ToList();
//Do not keep the last 10 students
List<Student> results = students.SkipLast(10).ToList();
//As long as the students are not in class one
List<Student> results =  students.SkipWhere (P = > p.class = = "first shift"). Tolist();
//The previous line of code is equivalent to= students.Where (P = > p.class! = "first shift"). Tolist();

Take takes several elements

Like skip, take also has three derived methods, and the declared parameter types are the same. I will not introduce the declaration here, just for example.

//Top 10 students
List<Student> results = students.Take(10).ToList();
//Select the last 10 students
List<Student> results = students.TakeLast(10).ToList();
//Select a class of students
List<Student> results =  students.TakeWhile (P = > p.class = = "first shift"). Tolist();
//The previous line is equivalent to= students.Where (P = > p.class = = "first shift"). Tolist();

When LINQ is used to write paging, take and skip are used together:

Int PageSize = 10; // 10 pieces of data per page
Int PageIndex = 1; // current first page
List<Student> results = students.Skip((pageIndex-1)*pageSize).Take(pageSize).ToList();

Where PageIndex can be any number greater than 0. The interesting thing about take and skip is that if the number passed in is larger than the data from the data source, it will not explode at all, but will return an empty data source list.

Select select

The official explanation for select is to project each element of the sequence into a new form. My understanding is that I define a converter for a single object of data source, and then process the data in my own way, select some fields, and transform some fields.

So as I understand it, I can’t find the same effect method of java8. (in fact, Java uses map, so it can’t be found,: – D)

public static System.Collections.Generic.IEnumerable<TResult> Select<TSource,TResult> (this IEnumerable<TSource> source, Func<TSource,TResult> selector);

Example:

//Select class and name
List<object> results = students.Select(p => new
{
    p.Class,
    p.Name
}).ToList();

Simple operation

LINQ has several simple operations that need attention, which are very common in use.

Max selects the largest one

Max gets the largest data source, but it can only be of number type. Other types can’t directly compare sizes, so there is an alternative method, which is to sort first.

Here are two overloaded versions of the max method:

public static int Max (this IEnumerable<int> source);
public static int Max <TSource>(this IEnumerable<TSource> source,Func<TSource,int> selector);

Example:

//How old is the oldest student
int maxAge = students.Select(t=>t.Age).Max();

Min selects the smallest

Method is similar to max, but it is different from getting the smallest one and cannot be applied to non numeric types.

Example:

//Find out the youngest age among the students
int minAge = students.Select(t=> t.Age).Min();
//=======
int minAge = students.Min(p=>p.Age);

Average to average

It is the same type of method as Max / min and still cannot be applied to non numeric types.

Example:

//Query the evaluation age of students
int averageAge = students.Select(t=>t.Age).Average();
int averageAge = students.Average(p=>p.Age);

Sum

Sum the data source or a field of the data source, or sum the non numeric type

Example:

//A summation without practical significance, the summation of students' ages
int sumAge = students.Select(t=>t.Age).Sum();
//
int sumAge = students.Sum(p=>p.Age);

Whether contains contains an element

To determine whether an element is included in the data source, return a bool value, true if it is included, and false if it is not included. There are two overloaded versions of this method, one is using the defaultEqualsMethod, one is to specify an equality comparer implementation class.

public static bool Contains<TSource> (this IEnumerable<TSource> source, TSource value);

//Of the incoming equality comparator
public static bool Contains<TSource> (this IEnumerable<TSource> source, TSource value, IEqualityComparer<TSource> comparer);

It is worth noting that the equality comparer here is an interface, that is, you need to use classes to implement this method. Usually in the actual development process, we will add the implementation of iequalitycompare on the class represented by tSource.

Example 1:

Student student1 = new student(); // initialize a student class
Student student2 =  students.First (); // take one from the data source

bool isContains =  students.Contains (student1); // false is returned,
bool isContains2 =  students.Contains (student2); // returns true

Note: the default equality comparison of a class is to compare whether it is the same object, that is, to return

Example 2:

To create an equality comparator, it is worth noting that there are two methods for the equality comparator: one is to compare whether the elements are equal, and the other is to return the hashcode of the elements. These two methods must be consistent in determining whether the elements are equal.

public class StudentEqualityCompare: IEqualityComparer<Student>
{
    public bool Equals(Student x, Student y)
    {
        //Omit logic
    }

    public int GetHashCode(Student obj)
    {
        //Omit logic
    }
}

use:

StudentEqualityCompare compare = new StudentEqualityCompare();
Student student = students.First();
bool isContains = students.Contains(student, compare);

Count / longcount quantity query

This is a set of methods with the same behavior, that is, counting the data source. The difference is that count returns int and longcount returns long.

They can be declared in the following two ways: count is selected here:

public static int Count<TSource> (this IEnumerable<TSource> source);

public static int Count<TSource> (this IEnumerable<TSource> source, Func<TSource,bool> predicate);

Example:

int count =  students.Count (); // returns the total number of students
int count =  students.Count (P = > p.class = = "class 1"); // count the total number of students in class 1

Operation of data source of the same type

The operation methods of a single data source are introduced before. These methods will not change the data source, and will filter, select or count more data sources. Now I will introduce several methods to operate on multiple data sources.

Union federates another data source of the same type

Combining another data source means merging two data sources into one, removing duplicate elements, reserving only non duplicate elements, and returning the result set.

Similar to the contains method, this method has two overloaded versions:

public static IEnumerable<TSource> Union<TSource> (this IEnumerable<TSource> first, IEnumerable<TSource> second);

public static IEnumerable<TSource> Union<TSource> (this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer);

Example:

First, suppose a business scenario:

The school holds the sports meeting. Now the academic affairs office has received the application list of the track and field team for the 500m run and the long jump. We need to see which students have signed up for these two events.

//Omit data source, track and field group list
IEnumerable<Student> students1 = new List<Student>();
//Omit data source, long jump group list
IEnumerable<Student> students2 = new List<Student>();


List<Student> all = students1.Union(student2).ToList();

At this time, we simply counted all the people, but later when the Academic Affairs Office checked, it was found that some people’s names were duplicate, and it was necessary to determine whether they were one person, so we had to create an equal comparator.

List<Student> all = students1.Union(student2,compare).ToList();
//The implementation of compare is omitted. For details, refer to the comparator of contains

Intersect gets data that exists in both sets

Gets elements that exist in both collections, similar to union.

The method is declared as follows:

public static IEnumerable<TSource> Intersect<TSource> (this IEnumerable<TSource> first, IEnumerable<TSource> second);

public static IEnumerable<TSource> Intersect<TSource> (this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer);

Example:

To continue the previous business scenario, now the academic affairs office needs to know which students have signed up for two competitions at the same time

List<Student> students = students1.Intersect(students2).ToList();

Exception gets data that only exists in the first data source

Get the elements that only exist in the first set, remove the elements that exist in the first set and the second set at the same time, and return.

The method is declared as follows:

public static IEnumerable<TSource> Except<TSource> (this IEnumerable<TSource> first, IEnumerable<TSource> second);

public static IEnumerable<TSource> Except<TSource> (this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer);

Example:

To continue the business description, the academic affairs office needs a list of students who have only signed up for 500 meters

List<Student> students = students1.Except(students2).ToList();

Reverse flip order

The elements in the data source originally have a certain order. This method can turn the order in the data source over. The last one becomes the first one

, the first becomes the last.

Simple example:

char[] apple = { 'a', 'p', 'p', 'l', 'e' };

char[] reversed = apple.Reverse().ToArray();

Distinct de duplication

De duplicate the data source and return the result after de duplication. Again, there are two overloaded versions of this method, one with comparator and one without comparator.

//Without comparator
public static IEnumerable<TSource> Distinct<TSource> (this IEnumerable<TSource> source);
//Set comparator
public static IEnumerable<TSource> Distinct<TSource> (this IEnumerable<TSource> source, IEqualityComparer<TSource> comparer);

Example:

First, describe a possible scene. Each class is a bit confused when submitting the application information of each competition group. The teacher in charge of 500m recorded the list of a class more than once, but the students are out of order. Now we need to remove the extra record, that is, to de duplicate the data.

List<Student> students = students1.Distinct();

Operations for multiple types of data sources

The previous methods basically operate on one type of data source and do not involve other types of data sources. Now I’ll show you how to associate multiple types of data sources, similar to multi table linked queries in SQL.

Join associates two data sources

Associate the two data sources according to certain logic, and then select the required data.

Methods have these overloaded versions:

public static IEnumerable<TResult> Join<TOuter,TInner,TKey,TResult> (this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter,TKey> outerKeySelector, Func<TInner,TKey> innerKeySelector, Func<TOuter,TInner,TResult> resultSelector);

//
public static IEnumerable<TResult> Join<TOuter,TInner,TKey,TResult> (this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter,TKey> outerKeySelector, Func<TInner,TKey> innerKeySelector, Func<TOuter,TInner,TResult> resultSelector, IEqualityComparer<TKey> comparer);

There are many parameters in this method. Let’s briefly introduce all the parameters of this method:

Type parameter

  • The type of element in the first sequence of Touter.
  • The type of element in tinner’s second sequence.
  • The type of key returned by the tkey selector function.
  • Type of tresult result element.

parameter

  • Outer IEnumerable < Touter > the first sequence to join.
  • Inner IEnumerable < tinner > the sequence to join with the first sequence.
  • Outerkeyselector func < tooter, tkey > function used to extract join keys from each element of the first sequence.
  • Innerkeyselector func < tinner, tkey > the function used to extract the join key from each element of the second sequence.
  • Resultselector func < tooter, tinner, tresult > function used to create result elements from two matching elements.
  • Compariequalitycomparer < tkey > the iequalitycomparer used to hash and compare keys.

Example:

Suppose that the Chinese teacher had organized an exam the day before yesterday. Because it was a simulated formal exam, the students on the answer sheet only wrote the student number. Now we need to associate the exam results with the students

List<object> results = students.Join(subjects,
                                     p => p.StudentId, 
                                     s => s.StudentId, 
                                     (p, s) => new 
                                     {
                                         Student = p, 
                                         Subject = s
                                     }).ToList();
/**
Return an anonymous object of a student and subject, but I've connected it with object. Here's a question. If you are interested, you can learn about the VaR keyword and anonymous object of C #, which will be put in the basic series of C ා to complete the whole explanation
*/

Groupjoin associates and groups two data sources

Based on the key equivalence, the elements of the two sequences are associated and the results are grouped. The above is an official introduction. I haven’t used this method in the development process, but this method can be regarded as a combination of join and group, that is, a join and then a group of data.

Method statement:

//Use default comparator
public static IEnumerable<TResult> GroupJoin<TOuter,TInner,TKey,TResult> (this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter,TKey> outerKeySelector, Func<TInner,TKey> innerKeySelector, Func<TOuter,IEnumerable<TInner>,TResult> resultSelector);
//Set comparator
public static IEnumerable<TResult> GroupJoin<TOuter,TInner,TKey,TResult> (this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter,TKey> outerKeySelector, Func<TInner,TKey> innerKeySelector, Func<TOuter,IEnumerable<TInner>,TResult> resultSelector, IEqualityComparer<TKey> comparer);

Type parameter

  • The type of element in the first sequence of Touter.
  • The type of the element in tinner’s second sequence.
  • The type of key returned by the tkey key selector function.
  • Type of tresult result element.

parameter

  • Outer IEnumerable < Touter > the first sequence to join.
  • Inner IEnumerable < tinner > the sequence to join with the first sequence.
  • Outerkeyselector func < tooter, tkey > function used to extract join keys from each element of the first sequence.
  • Innerkeyselector func < tinner, tkey > the function used to extract the join key from each element of the second sequence.
  • Resultselector function < tooter, IEnumerable < tinner >, tresult > is a function used to create a result element from a set of matching elements of the first sequence and the second sequence.
  • Comparer iequalitycomparer < tkey > the iequalitycomparer used to hash and compare keys.

Here is an official example:

class Person
{
    public string Name { get; set; }
}

class Pet
{
    public string Name { get; set; }
    public Person Owner { get; set; }
}

public static void GroupJoinEx1()
{
    Person magnus = new Person { Name = "Hedlund, Magnus" };
    Person terry = new Person { Name = "Adams, Terry" };
    Person charlotte = new Person { Name = "Weiss, Charlotte" };

    Pet barley = new Pet { Name = "Barley", Owner = terry };
    Pet boots = new Pet { Name = "Boots", Owner = terry };
    Pet whiskers = new Pet { Name = "Whiskers", Owner = charlotte };
    Pet daisy = new Pet { Name = "Daisy", Owner = magnus };

    List<Person> people = new List<Person> { magnus, terry, charlotte };
    List<Pet> pets = new List<Pet> { barley, boots, whiskers, daisy };

    // Create a list where each element is an anonymous 
    // type that contains a person's name and 
    // a collection of names of the pets they own.
    var query =
        people.GroupJoin(pets,
                         person => person,
                         pet => pet.Owner,
                         (person, petCollection) =>
                             new
                             {
                                 OwnerName = person.Name,
                                 Pets = petCollection.Select(pet => pet.Name)
                             });

    foreach (var obj in query)
    {
        // Output the owner's name.
        Console.WriteLine("{0}:", obj.OwnerName);
        // Output each of the owner's pet's names.
        foreach (string pet in obj.Pets)
        {
            Console.WriteLine("  {0}", pet);
        }
    }
}

/*
 This code produces the following output:

 Hedlund, Magnus:
   Daisy
 Adams, Terry:
   Barley
   Boots
 Weiss, Charlotte:
   Whiskers
*/

This is all about LINQ’s methods, but it’s not all about LINQ. There will be a follow-up article about another query method of LINQ.

Please pay more attention to my blog “Mr. Gao’s cabin”

C ා basic knowledge series-7 LINQ details