LINQ基礎(二)

来源:http://www.cnblogs.com/afei-24/archive/2017/05/12/6845551.html
-Advertisement-
Play Games

本文主要介紹LINQ查詢操作符 LINQ查詢為最常用的操作符定義了一個聲明語法。還有許多查詢操作符可用於Enumerable類。 下麵的例子需要用到LINQ基礎(一)(http://www.cnblogs.com/afei-24/p/6841361.html)的一些代碼 1.篩選 LINQ查詢使用w ...


  本文主要介紹LINQ查詢操作符
  LINQ查詢為最常用的操作符定義了一個聲明語法。還有許多查詢操作符可用於Enumerable類。

  下麵的例子需要用到LINQ基礎(一)(http://www.cnblogs.com/afei-24/p/6841361.html)的一些代碼

1.篩選
  LINQ查詢使用where子句添加條件表達式來篩選,where子句可以合併多個表達式。

var racers = from r in Formula1.GetChampions() 
                    where r.Wins>15 && 
                        (r.Country == "Brazil" || r.Country =="Austria")
                    select r;
                    
        foreach(var r in racers)
        {
            Console.WriteLine("{0:A}", r);
        }

  上述LINQ表達式映射為C# LINQ查詢的擴展方法:
  var racers = Formula1.GetChampions().Where(r =>where r.Wins>15 &&
    (r.Country == "Brazil" || r.Country =="Austria")).Select(r => r);
  註意,並不是所以查詢都可以使用LINQ查詢語法,也不是所有的擴展方法都映射到LINQ查詢。高級查詢需要使用擴展方法。

2.用索引篩選
  不能使用LINQ查詢的一個例子是Where()方法的重載。在WHere()方法的重載中,可以傳遞第二個參數————索引。索引是篩選器返回的每個結果的計數器。可以在表達式中使用這個索引,執行基於索引的計算:

var racers = Formula1.GetChampions().
            Where((r, index) => r.LastName.StartsWith("A") && index % 2 != 0);
        foreach (var r in racers)
        {
            Console.WriteLine("{0:A}", r);
        }

 

3.類型篩選
  為了進行基於類型的篩選,可以使用OfType()擴展方法。

object[] data = { "one", 2, 3, "four", "five", 6 };
          var query = data.OfType<string>();
          foreach (var s in query)
          {
            Console.WriteLine(s);
          }

  輸出:
    one
    four
    five
  從集合僅返回字元串。

4.複合的from子句
  如果需要根據對象的成員進行篩選,而該成員本身是一個系列,就可以使用複合from子句。例如,LINQ基礎(一)(http://www.cnblogs.com/afei-24/p/6841361.html)中的Racer類定義了一個屬性Cars,Cars是一個字元串數組。

  篩選駕駛Ferrari的所以冠軍:  

  var ferrariDrivers = from r in Formula1.GetChampions()
                           from c in r.Cars
                           where c == "Ferrari"
                           orderby r.LastName
                           select r.FirstName + " " + r.LastName;

      foreach (var racer in ferrariDrivers)
      {
        Console.WriteLine(racer);
      }

 

  第一個from子句訪問Formula1.GetChampions()方法返回的Racer對象,第二個from子句訪問Racer類的Cars屬性,以返回所以sting類型的賽車。

  C#編譯器把複合的from子句和LINQ查詢轉換為SelectMany()擴展方法。SelectMany()擴展方法可以迭代序列中的序列。
  SelectMany()的重載版本:
  public static IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>(this IEnumerable<TSource> source,
    Func<TSource, IEnumerable<TCollection>> collectionSelector,
      Func<TSource, TCollection, TResult> resultSelector);
  第一個參數是隱式參數,它從Formula1.GetChampions()方法接受Racer對象序列。第二個參數是collectionSelector委托,其中定義了內部序列,是序列的序列,本例子為Cars。第三個參數也是一個委托,為每個Racer對象的Cars屬性的每個元素調用這個委托。
  這裡Cars是一個字元串數組,會將每個Racer和每個字元串作為參數,調用這個委托。  

var ferrariDrivers = Formula1.GetChampions().SelectMany(
            c => c.Cars, (r, s) => new { Racer=r,Car =s}).Where(
            s =>s.Car == "Ferrari").OrderBy(
            r => r.Racer.LastName).Select(r => r.Racer.FirstName + " " + r.Racer.LastName);

        foreach (var racer in ferrariDrivers)
        {
            Console.WriteLine(racer);
        }

5.排序
  要對序列排序,可以使用前面使用過的orderby.也可以使用orderrby descending子句(降序)。 

var racers = (from r in Formula1.GetChampions()
                      orderby r.Country  descending
                      select r);

        foreach (var racer in racers)
        {
            Console.WriteLine("{0}: {1}, {2}", racer.Country, racer.LastName, racer.FirstName);
        }

  orderby子句解析為OrderBy()方法,orderby r.Country descending解析為OrderByDescending()方法:
  var racers = Formula1.GetChampions().OrderByDescending(r => r.Country).Select(r=>r);
  OrderBy()和OrderByDescending()方法返回IOrderEnumerable<TSource>。這個介面派生自IEnumerable<TSource>介面,但包含一個額外的方法CreateOrderEnumerable<TSource>()方法。這個方法用於進一步給序列排序,可以在最後一個參數指定升序還是降序:
  

// 摘要:
        //     根據某個鍵對 System.Linq.IOrderedEnumerable<TElement> 的元素執行後續排序。
        //
        // 參數:
        //   keySelector:
        //     用於提取每個元素的鍵的 System.Func<T,TResult>。
        //
        //   comparer:
        //     用於比較鍵在返回序列中的位置的 System.Collections.Generic.IComparer<T>。
        //
        //   descending:
        //     如果為 true,則對元素進行降序排序;如果為 false,則對元素進行升序排序。
        //
        // 類型參數:
        //   TKey:
        //     keySelector 生成的鍵的類型。
        //
        // 返回結果:
        //     一個 System.Linq.IOrderedEnumerable<TElement>,其元素按鍵排序。
        IOrderedEnumerable<TElement> CreateOrderedEnumerable<TKey>(Func<TElement, TKey> keySelector, IComparer<TKey> comparer, bool descending);


  例子:

// Create an array of strings to sort.
              string[] fruits = { "apricot", "orange", "banana", "mango", "apple", "grape", "strawberry" };
              // First sort the strings by their length.
              IOrderedEnumerable<string> sortedFruits2 =
                  fruits.OrderBy(fruit => fruit.Length);
              // Secondarily sort the strings alphabetically, using the default comparer.
              IOrderedEnumerable<string> sortedFruits3 =
                  sortedFruits2.CreateOrderedEnumerable<string>(
                      fruit => fruit,
                      Comparer<string>.Default, false);

  使用ThenBy和ThenByDescending()方法進行進一步排序,可以添加任意多個:
  var racers = Formula1.GetChampions().OrderByDescending(r => r.Country).ThenByDescending(
    r => r.LastName).ThenByDescending(r => r.FirstName).Select(r => r);

6.分組
  要根據一個關鍵字值對查詢結果分組,可以使用group子句。

// group r by r.Country into g 根據Country屬性組合所有的賽車手,並定義為一個新的集合g,用於訪問分組的結果信息。
        //select子句創建一個帶Country和Count屬性的匿名類型。Country = g.Key Key是r.Country
        var countries = from r in Formula1.GetChampions()
                      group r by r.Country into g
                      orderby g.Count() descending, g.Key
                      where g.Count() >= 2
                      select new
                      {
                        Country = g.Key,
                        Count = g.Count()
                      };
        foreach (var item in countries)
          {
            Console.WriteLine("{0, -10} {1}", item.Country, item.Count);
          }

  輸出:
  

  使用擴展方法執行相同的操作,把group r by r.Country 子句解析為GroupBy()方法。在GroupBy()方法的聲明中,它返回實現了IGrouping<TKey, TSource>介面的枚舉對象。IGrouping<TKey, TSource>介面定義了Key屬性,所以在調用了這個方法後,可以訪問分組的關鍵字:
  public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector);
  使用GroupBy方法:

 var countries = Formula1.GetChampions().GroupBy(r => r.Country).OrderByDescending(
            g => g.Count()).ThenBy(g => g.Key).Where(g => g.Count() >= 2).Select(
            g=>new
                      {
                          Country = g.Key,
                          Count = g.Count()
                      });


7.對嵌套的對象分組
  如果得到的分組的對象需要包含嵌套的序列,就可以改變select子句創建匿名類型。
  

//返回的對象不僅需要包含國家名和賽車手這兩個屬性,還應包含賽車手集合。
        //使用from r1 in g orderby r1.LastName select r1.FirstName + " " + r1.LastName 內部子句
        var countries = from r in Formula1.GetChampions()
                      group r by r.Country into g
                      orderby g.Count() descending, g.Key
                      where g.Count() >= 2
                      select new
                      {
                        Country = g.Key,
                        Count = g.Count(),
                        Racers = from r1 in g
                                 orderby r1.LastName
                                 select r1.FirstName + " " + r1.LastName
                      };
      foreach (var item in countries)
      {
        Console.WriteLine("{0, -10} {1}", item.Country, item.Count);
        foreach (var name in item.Racers)
        {
          Console.Write("{0}; ", name);
        }
        Console.WriteLine();
      }


8.內連接
  使用join子句可以根據特定的條件合併兩個數據源,但之前要獲得兩個連接的列表。
  使用了LINQ基礎(一)(http://www.cnblogs.com/afei-24/p/6841361.html)的代碼  

//GetChampions獲得冠軍賽車手
        var racers = from r in Formula1.GetChampions()
                   from y in r.Years
                   select new
                   {
                     Year = y,
                     Name = r.FirstName + " " + r.LastName
                   };
        //GetContructorChampions獲取冠軍車隊
          var teams = from t in Formula1.GetContructorChampions()
                      from y in t.Years
                      select new
                      {
                        Year = y,
                        Name = t.Name
                      };
        //得到每一年獲得冠軍的賽車手和車隊
        //通過join t in teams on r.Year equals t.Year into rt 子句連接兩個數據源
          var racersAndTeams =
            (from r in racers
             join t in teams on r.Year equals t.Year into rt
             from t in rt.DefaultIfEmpty()
             orderby r.Year
             select new
             {
               Year = r.Year,
               Champion = r.Name,
               Constructor = t == null ? "no constructor championship" : t.Name
             });

          Console.WriteLine("Year  Champion\t\t   Constructor Title");
          foreach (var item in racersAndTeams)
          {
            Console.WriteLine("{0}: {1,-20} {2}",
               item.Year, item.Champion, item.Constructor);
          }

 

 

9.左連接
  使用內連接返回匹配r.Year equals t.Year的結果。左連接返回左邊數據源的全部元素,即使在右邊的數據源中沒有匹配的元素。
  

var racers = from r in Formula1.GetChampions()
                   from y in r.Years
                   select new
                   {
                     Year = y,
                     Name = r.FirstName + " " + r.LastName
                   };

      var teams = from t in Formula1.GetContructorChampions()
                  from y in t.Years
                  select new
                  {
                    Year = y,
                    Name = t.Name
                  };
        //左連接用join和DefaultIfEmpty方法定義。
        //如果查詢到左側數據源沒有和右邊數據源Year相同的結果,使用DefaultIfEmpty方法定義右側的預設值(為空)
      var racersAndTeams =
        (from r in racers
         join t in teams on r.Year equals t.Year into rt
         from t in rt.DefaultIfEmpty()
         orderby r.Year
         select new
         {
           Year = r.Year,
           Champion = r.Name,
           Constructor = t == null ? "no constructor championship" : t.Name
         });

      Console.WriteLine("Year  Champion\t\t   Constructor Title");
      foreach (var item in racersAndTeams)
      {
        Console.WriteLine("{0}: {1,-20} {2}",
           item.Year, item.Champion, item.Constructor);
      }


10.組連接
  組連接類似內連接,內連接通過某一項連接兩個數據源(如 r.Year equals t.Year),組連接使用一組項連接,例如下麵的例子,
  通過 new
    {
      FirstName = r.FirstName,
      LastName = r.LastName
    }
    equals
    new
    {
      FirstName = r2.FirstName,
      LastName = r2.LastName
    }
  連接兩個數據源
  

var racers = Formula1.GetChampionships()
        .SelectMany(cs => new List<RacerInfo>()
        {
         new RacerInfo {
           Year = cs.Year,
           Position = 1,
           FirstName = cs.First.FirstName(),
           LastName = cs.First.LastName()        
         },
         new RacerInfo {
           Year = cs.Year,
           Position = 2,
           FirstName = cs.Second.FirstName(),
           LastName = cs.Second.LastName()        
         },
         new RacerInfo {
           Year = cs.Year,
           Position = 3,
           FirstName = cs.Third.FirstName(),
           LastName = cs.Third.LastName()        
         }
       });

      var q = (from r in Formula1.GetChampions()
               join r2 in racers on
               new
               {
                 FirstName = r.FirstName,
                 LastName = r.LastName
               }
               equals
               new
               {
                 FirstName = r2.FirstName,
                 LastName = r2.LastName
               }
               into yearResults
               select new
               {
                 FirstName = r.FirstName,
                 LastName = r.LastName,
                 Wins = r.Wins,
                 Starts = r.Starts,
                 Results = yearResults
               });

      foreach (var r in q)
      {
        Console.WriteLine("{0} {1}", r.FirstName, r.LastName);
        foreach (var results in r.Results)
        {
          Console.WriteLine("{0} {1}", results.Year, results.Position);
        }
      }

 

11.集合操作
  擴展方法Distinct(),Union(),Intersect()(獲取交集),Except()都是集合操作。  

//獲取同時駕駛Ferrari和駕駛McLaren獲得過冠軍的賽車手
        static void SetOperations()
        {
            //定義一個委托,用來查詢駕駛Ferrari獲得過冠軍的賽車手和駕駛McLaren獲得過冠軍的賽車手
          Func<string, IEnumerable<Racer>> racersByCar =
              car => from r in Formula1.GetChampions()
                     from c in r.Cars
                     where c == car
                     orderby r.LastName
                     select r;

          Console.WriteLine("World champion with Ferrari and McLaren");
          //使用Intersect方法獲取兩個數據源的交集
          foreach (var racer in racersByCar("Ferrari").Intersect(racersByCar("McLaren")))
          {
            Console.WriteLine(racer);
          }
        }

 

12.合併
  Zip()方法是.NET 4.0新增的,允許用一個為此函數把兩個相關的序列合併為一個。
  對於合併,第一個集合中的第一項與第二個集合的第一項合併,第一個集合中的第二項與第二個集合的第二項合併,以此類推。如果兩個序列的項數不同,Zip()方法就會在達到較小集合的末尾時停止。

  第一個集合中的元素有一個Name屬性,第二個集合中的元素有LastName和Starts屬性。
  在racerNames集合上使用Zip()方法,需要把第二個集合racerNamesAndStarts作為第一個參數。第二個參數是一個委托,它通過參數first接受第一個集合的元素,通過參數second接受第二個集合的元素。其實現代碼返回一個字元串:  

var racerNames = from r in Formula1.GetChampions()
                       where r.Country == "Italy"
                       orderby r.Wins descending
                       select new
                       {
                         Name = r.FirstName + " " + r.LastName
                       };

          var racerNamesAndStarts = from r in Formula1.GetChampions()
                                    where r.Country == "Italy"
                                    orderby r.Wins descending
                                    select new
                                    {
                                      LastName = r.LastName,
                                      Starts = r.Starts
                                    };


          var racers = racerNames.Zip(racerNamesAndStarts, (first, second) => first.Name + ", starts: " + second.Starts);
          foreach (var r in racers)
          {
              Console.WriteLine(r);
          }


13.分區
  擴展方法Take()和Skip()等的分區操作可用於分頁。
  例如,在第一頁只顯示5個賽車手,下一頁顯示接下來的5個賽車手...
  Skip(page * pageSize)方法調到指定索引出,忽略前面的數據。Take(pageSize)方法顯示pageSize條數據
  

 int pageSize = 5;

          int numberPages = (int)Math.Ceiling(Formula1.GetChampions().Count() /
                (double)pageSize);

          for (int page = 0; page < numberPages; page++)
          {
            Console.WriteLine("Page {0}", page);

            var racers =
               (from r in Formula1.GetChampions()
                orderby r.LastName, r.FirstName
                select r.FirstName + " " + r.LastName).
               Skip(page * pageSize).Take(pageSize);

            foreach (var name in racers)
            {
              Console.WriteLine(name);
            }
            Console.WriteLine();
          }

  TakeWhile()和SkipWhile()方法,傳遞一個委托,滿足這個條件的數據就提取或跳轉:
  public static IEnumerable<TSource> SkipWhile<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);

14.聚合操作符
  聚合操作符(如Count(),Sum(),Min(),Max(),Average(),Aggregate())不返回一個序列,而是返回一個值。
  例如,使用Count方法應用於Racer的Years屬性,篩選獲得冠軍次數超過3次的賽車手。因為多次使用r.Years.Count(),所以使用let子句定義了一個變數。  

  

var query = from r in Formula1.GetChampions()
                  let numberYears = r.Years.Count()
                  where numberYears >= 3
                  orderby numberYears descending, r.LastName
                  select new
                  {
                    Name = r.FirstName + " " + r.LastName,
                    TimesChampion = numberYears
                  };

          foreach (var r in query)
          {
            Console.WriteLine("{0} {1}", r.Name, r.TimesChampion);
          }

  Aggregate()方法傳遞一個委托,將數據源中的每個元素作為委托的參數,並使用指定的函數累加。

15.轉換操作符
  LINQ基礎(一)(http://www.cnblogs.com/afei-24/p/6841361.html)提到,查詢會推遲到迭代數據項時才執行,使用轉換操作符會立即執行查詢,把查詢結果放在數組,列表和字典中。  

    

//轉換為數組
        var names = new List<string> { "Nino", "Alberto", "Juan", "Mike", "Phil" };

        var namesWithJ = (from n in names
                         where n.StartsWith("J")
                         orderby n
                         select n).ToList();



  轉換為Lookup<TKey,TElement>
  

//把Car賽車屬性作為鍵,每個鍵關聯多個車手Racer
            var racers = (from r in Formula1.GetChampions()
                      from c in r.Cars
                      select new
                      {
                          Car = c,
                          Racer = r
                          }).ToLookup(cr => cr.Car, cr => cr.Racer);
            foreach (var v in racers)
            {
                Console.Write(v.Key+"........");
                foreach (var k in racers[v.Key])
                {
                    Console.WriteLine(k);
                }
            }


  ToLookup(cr => cr.Car, cr => cr.Racer)方法的一個重載版本傳遞一個鍵和一個元素選擇器

  如果需要在非類型化的集合上使用LINQ查詢,可以使用Cast()方法,定義強類型化的查詢:  

var list = new System.Collections.ArrayList(Formula1.GetChampions() as System.Collections.ICollection);

          var query = from r in list.Cast<Racer>()
                      where r.Country == "USA"
                      orderby r.Wins descending
                      select r;
          foreach (var racer in query)
          {
            Console.WriteLine("{0:A}", racer);
          }

 

  Cast<Racer>()將 System.Collections.IEnumerable 的元素強制轉換為指定的類型。

16.生成操作符
  生成操作符Range(),Empty(),Repeat()方法不是擴展方法,而是返回序列的正常靜態方法。在LING to Object中,這些方法可用於Enumerable類。
  Range()方法用來填充一個範圍的數字。第一個參數作為起始值,第二個參數作為要填充的項數:
    var values = Enumerable.Range(1,20);
  結果為1至20的集合。


  可以把該結果與其它擴展方法合併:
    var values = Enumerable.Range(1,20).Select(n=> n*3);

  Empty()方法返回一個不返回值的迭代器,它用於需要一個集合的參數,其中可以給參數傳遞空集合。
  Repeat()方法返回指定個數的重覆值的集合迭代器。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...