.NET Core3.1 LINQ查詢


1、LINQ介紹

  LINQ(Language Integrated Query,語言集成查詢),是c#編程語言中的一種查詢語法。有了LINQ,使得以相同的語法訪問不同的數據源成為可能。這是因為,LINQ提供了不同數據源的抽象層。

2、LINQ查詢基礎

  本節介紹了一個簡單的LINQ查詢。在此基礎上,着重理解:c#提供了轉換為方法調用的集成查詢語言。

  2.1、准備列表和實體

    接下來的幾個章節的LINQ查詢是在一個錦標賽的集合上進行的。這個集合需要用到.NET的類庫:

      System

      System.Collection.Generic

  2.2、Racer類型。

    Racer定義了幾個屬性和一個重載的ToString()方法,該方法以字符串格式顯示賽車手。實現了IFormattable接口,以支持字符串的不同變體;還實現了IComparable<Racer>接口,它根據Lastname為一組賽車手排序。為了演示更高級的查詢,Racer類還定義了多值屬性,如Cars和Years。Years屬性列出了賽車手獲得冠軍的年份(車手可以多次獲得冠軍)。Cars屬性列出了賽車手在獲得冠軍的年份中使用的所有車型:

 1     //這是一個賽車手
 2     public class Racer:IComparable<Racer>,IFormattable
 3     {
 4         public string FirstName { get; }
 5         public string LastName { get; } 
 6         public string Country { get; }  
 7         public int Starts { get; }      
 8         public int Wins { get; }        
 9         public object Years { get; }       //包含車手獲得冠軍的年份(可以多次獲得冠軍)
10         public object Cars { get; }       //車手獲得冠軍年份中使用的車型。參加不同比賽,使用的車型可能不一樣
11 
12         public Racer(string firstName,string lastName,string country,int starts,int wins)
13             :this(firstName,lastName,country,starts,wins,null,null){ }
14 
15         public Racer(string firstName, string lastName, string country, int starts, int wins, IEnumerable<int> years, IEnumerable<string> cars)
16         {
17             FirstName = firstName;
18             LastName = lastName;
19             Country = country;
20             Starts = starts;
21             Wins = wins;
22             Years = years;
23             Cars = cars;
24         }
25         public override string ToString() => $"{FirstName} {LastName}";
26 
27         int IComparable<Racer>.CompareTo(Racer other) => FirstName.CompareTo(other?.FirstName);
28 
29         public string ToString(string format) => ToString(format, null);
30 
31         public string ToString(string format, IFormatProvider formatProvider)
32         {
33             switch (format)
34             {
35                 case null:
36                 case "N":
37                     return ToString();
38                 case "F":
39                     return FirstName;
40                 case "L":
41                     return LastName;
42                 case "C":
43                     return Country;
44                 case "S":
45                     return Starts.ToString();
46                 case "W":
47                     return Wins.ToString();
48                 case "A":
49                     return $"姓名:{FirstName} {LastName},國家:{Country}; 開始:{Starts}; 獲勝次數:{Wins}";
50                 default:
51                     throw new FormatException($"不支持格式:{format}");
52             }
53         }
54     }
View Code

  2.3、Team類

    這個類僅包含車隊冠軍的名字和獲得冠軍的年份。

    //包含冠軍姓名和獲得冠軍的年份
    public class Team
    {
        public string Name { get; }
        public IEnumerable<int> Years { get; }
        public Team(string name,params int[] years)
        {
            Name = name;
            Years = years != null ? new List<int>(years) : new List<int>();
        }
    }
View Code

  2.4、Formulal類

    該類的GetChampions()方法返回了一組賽車手。GetConstructorChampions()方法返回所有車隊冠軍的列表:

    public static class Formulal
    {
        private static List<Racer> s_racers;

        public static IList<Racer> GetChampions() => s_racers ?? InitalizeRacers();

        private static List<Racer> InitalizeRacers() => new List<Racer>
        {
            new Racer("",   "","中國",22,55,new int[] {1952,1925},     new string[]{ "賓利", "法拉利"}),
            new Racer("",   "","美國",22,55,new int[] {1967,1976},     new string[]{ "豐田","奧迪"}),
            new Racer("",   "","日本",22,55,new int[] {1974,1947},     new string[]{ "本田","大眾"}),
            new Racer("",   "","朝鮮",22,55,new int[] {1951,1915},     new string[]{ "長安","長安"}),
            new Racer("",   "","英國",22,55,new int[] {1952,1925},     new string[]{ "奧迪", "馬自達"}),
            new Racer("",   "","德國",22,55,new int[] {1945,1954,2054},new string[]{ "大眾","迪奧"}),
            new Racer("",   "","法國",22,55,new int[] {1928,1982,2082},new string[]{ "長安","特斯拉"}),
            new Racer("",   "","瑞典",22,55,new int[] {1989,1998,2098},new string[]{ "奧迪", "賓利"}),
            new Racer("",   "","印度",22,55,new int[] {1986,1968,2068},new string[]{ "大眾","豐田"}),
            new Racer("Marry","sa","韓國",22,55,new int[] {1966,1966,2066},new string[]{ "長安","本田"}),
            new Racer("Jack", "jw","中國",22,55,new int[] {1963,1936,2036},new string[]{ "奧迪", "長安"}),
            new Racer("WC",   "sa","美國",22,55,new int[] {1930,1903,2003},new string[]{ "法拉利", "法拉利"})
        };

        private static List<Team> s_teams;
        public static IList<Team> GetConstructorChampions()
        {
            if(s_racers == null)
            {
                s_teams = new List<Team>()
                {
                    new Team("",   1958),
                    new Team("",   1922,1233,1933),
                    new Team("",   1932,1941),
                    new Team("",   1914,1942,1941),
                    new Team("",   1945),
                    new Team("",   1995,1988,1966,1978,1935,1968),
                    new Team("",   2015,20145,2058),
                    new Team("",   2055),
                    new Team("",   2001,2015),
                    new Team("Marry",2016,2047),
                    new Team("Jack", 2055,2044),
                    new Team("WC",   2041,2087)
                };
            }
            return s_teams;
        }
    }
View Code

  2.5、LINQ查詢

  數據准備完畢,下面開始演示LINQ的應用。這是一個控制台應用程序。

  例如,查詢來自中國的所有冠軍,並按照奪冠次數降序排序:

using System;
using System.Collections.Generic;
using System.Linq;

namespace LINQDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            LINQQuery();
            ExtensionMethod();
        }
        /// <summary>
        /// Linq
        /// </summary>
        static void LINQQuery()
        {
            var query = from r in Formulal.GetChampions()
                        where r.Country =="中國"
                        orderby r.Wins descending
                        select r;
            foreach(var r in query)
            {
                Console.WriteLine($"{r:A}");
            }
        }
        static void ExtensionMethod()
        {
            var champions = new List<Racer>(Formulal.GetChampions());
            var champion = champions.Where(r => r.Country == "中國")
                .OrderByDescending(s => s.Wins)
                .Select(c=>c);
            foreach(var r in champion)
            {
                Console.WriteLine($"{r:A}");
            }
        }
    }
}

  運行結果:

  

 

   其中,LINQQuery()方法實現中主要演示了LINQ查詢的表達式:

            var query = from r in Formulal.GetChampions()
                        where r.Country =="中國"
                        orderby r.Wins descending
                        select r;

  from、where、orderby、descending和select都是預定義的關鍵字。查詢表達式必須以from子句開頭,以select或者group子句結束。這兩個子句之間,可以使用where、orderby、join、let和其他from子句。

  注意:變量query只是指定了LINQ查詢,該查詢不是通過這個賦值語句執行的,只要使用foreach循環訪問查詢,該查詢就會執行。

  ExtensionMethod()方法演示了LINQ的擴展方法的使用。下面重點介紹。

3、擴展方法

  定義:擴展方法在靜態類中聲明,定義為一個靜態方法,其中第一個參數定義了它擴展的類型。為了區分擴展方法和一般靜態方法,擴展方法需要對第一個參數使用this關鍵字。

  作用:①擴展方法可以將該方法寫入到類中(該類最初是沒有這個方法的);②開可以把方法添加到實現了某個特定接口的任何類中,這樣多個類就可以使用相同的實現代碼。

  例如,String類沒有Foo()方法,並且不能從封閉的類String繼承,但是可以創建一個擴展方法:

1 static class StringExtension
2     {
3         public static void Foo(this string s)
4         {
5             Console.WriteLine($"擴展方法Foo被調用:{s}");
6         }
7     }

  使用帶string類型的Foo()方法:

1   string s = "haha";
2   s.Foo();

  運行結果:

  

 

   也許這看起來違反了面向對象的規則,因為給一個類型定義了新方法,但沒有改變該類型或它的派生類。但實際上並非如此。擴展方法不能訪問它擴展的類型的私有成員。調用擴展方法只是調用靜態方法的一種新語法。對於上面例子中的字符串,可以使用如下方式調用Foo()方法,會獲得相同的結果:

string s = "haha";
StringExtension.Foo(s);

要調用靜態方法,應在類名的后面加上方法名。擴展方法是調用靜態方法的另一種形式。不必提供靜態方法的類名,相反,編譯器調用靜態方法是因為它帶的參數類型。

上面的例子中,只需要導入包含該類的名稱空間,就可以將Foo()擴展方法放在String類的作用域中。

 

  LINQ擴展方法

  編譯器會轉換LINQ查詢,以調用方法而不是LINQ查詢。定義LINQ擴展方法的一個類是System.Linq名稱空間中的Enumeable,它為IEnumerable<T>接口提供了各種擴展方法,以便在實現了該接口的任意集合上使用LINQ。只需要導入這個名稱空間,就可以打開這個類的擴展方法的作用域。

  下面是Where()擴展方法的實現代碼:

1     public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source,Func<TSource,bool> predicate)
2     {
3         foreach(TSource item in source)
4         {
5             if (predicate(item))
6                 yield return item;
7         }
8     }

  分析:Where()擴展方法的第一個參數包含了this關鍵字,類型是IEnumerable<T>。這樣Where()方法就可以用於實現了IEnumerable<T>接口的每個類型,例如:數組和List<T>類實現了IEnumerable<T>接口。第二個參數是一個Func(T,bool)委托(引用的是輸入參數類型為T、返回類型為布爾型的方法)。這個謂詞在實現代碼中調用,檢查IEnumerable<T>源中的項是否應該放在結果集合中。如果委托引用了該方法,yield return 語句就將源中的項返回給結果。

  因為Where()作為一個泛型方法實現,所以它可以用於包含在集合中的任意類型。實現了IEnumerable<T>接口的任意集合都支持它。

  到現在,就可以使用Enumerable類中的擴展方法Where()、OrderByDescending()和Select()。這些方法都返回IEnumerable<TSource>。通過擴展方法的參數,使用定義了委托參數的實現代碼的匿名方法。

 1         static void ExtensionMethod()
 2         {
 3             var champions = new List<Racer>(Formulal.GetChampions());
 4             var champion = champions.Where(r => r.Country == "中國")
 5                 .OrderByDescending(s => s.Wins)
 6                 .Select(c=>c);
 7             foreach(var r in champion)
 8             {
 9                 Console.WriteLine($"{r:A}");
10             }
11         }

4、推遲查詢的執行

  定義查詢表達式時,查詢並不會執行。查詢會在迭代數據項時運行。原因就是擴展方法Where(),它使用yield return返回謂詞為true的元素。這時,編譯器會創建一個枚舉器,在訪問枚舉中的項后,才返回他們。

  這會導致下面例子中有趣的現象,本例中創建一個string元素的集合,初始化它;然后給集合增加元素:

 1         static void DeferredQuery()
 2         {
 3             var name = new List<string> { "a1", "a2", "b", "c", "d" };
 4             var nameWithA = from n in name
 5                             where n.StartsWith("a")
 6                             orderby n
 7                             select n;
 8             foreach (var item in nameWithA)
 9             {
10                 Console.WriteLine($"{item}");
11             }
12             Console.WriteLine("更改數據源后,再次迭代同樣的LINQ語句:");
13             name.Add("a3");
14             name.Add("a4");
15             name.Add("a5");
16             foreach (var item in nameWithA)
17             {
18                 Console.WriteLine($"{item}");
19             }
20         }

 

  觀察結果,發現每次迭代時,可以檢測出源數據中的變化:  

  

 

   特殊情況,調用擴展方法ToArray()、ToList()等可以改變上述結果。下面示例中,ToList遍歷集合,返回一個實現了IList<string >的集合。然后對返回的列表遍歷兩次:

 1         private static void DeferredQueryDemo()
 2         {
 3             var name = new List<string> { "a1", "a2", "b", "c", "d" };
 4             var nameWhithA = (from n in name
 5                              where n.StartsWith("a")
 6                              orderby n
 7                              select n).ToList();
 8             Console.WriteLine("第一次迭代:");
 9             foreach(var iten in nameWhithA)
10             {
11                 Console.WriteLine(iten);
12             }
13             Console.WriteLine("更改數據源后,再次迭代同樣的LINQ語句:");
14             name.Add("a3");
15             name.Add("a4");
16             name.Add("a5");
17             foreach (var iten in nameWhithA)
18             {
19                 Console.WriteLine(iten);
20             }
21         }

 

   觀察結果,發現,雖然集合中的元素發生了變化,但是兩次迭代之間的的輸出保持不變:

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM