首頁 > 軟體

C#中的LINQ to Objects詳解

2022-05-19 19:02:16

一、概述

LINQ to Objects (C#) | Microsoft 官方檔案

“LINQ to Objects” 指直接將 LINQ 查詢與任何 IEnumerable 或 IEnumerable 集合一起使用。

可以使用 LINQ 來查詢任何可列舉的集合,例如 List、Array 或Dictionary<TKey,TValue>。 該集合可以是使用者定義的集合,也可以是由 .NET Framework API 返回的集合。

“LINQ to Objects”表示一種新的處理集合的方法。 採用舊方法,必須編寫指定如何從集合檢索資料的複雜的 foreach 迴圈。 而採用 LINQ 方法,只需編寫描述要檢索的內容的宣告性程式碼。

此外,LINQ 查詢與傳統 foreach 迴圈相比具有三大優勢:

  • 它們更簡明、更易讀,尤其在篩選多個條件時。
  • 它們使用最少的應用程式程式碼提供強大的篩選、排序和分組功能。
  • 無需修改或只需做很小的修改即可將它們移植到其他資料來源。

二、 Linq to Objects中的延遲計算

Linq查詢的延遲計算原理:通過給LINQ擴充套件方法傳遞方法委託,作為yield迭代器的主體,讓遍歷執行到MoveNext()時才執行耗時的指令。

1. Linq延遲計算的注意點

以下程式碼我們原本的期望結果是:刪除掉字串中所有的原音字母。但現在只刪除’u’,因為item變數是迴圈外部宣告的,同一個變數重複宣告更新,所以當最後賦值時,item記錄的是最後一個值為'u'。在foreach才真正執行Where查詢。

IEnumerable<char> query = "Not what you might expect";
var item = 'a';
query = query.Where(c => c != item);
item = 'e';
query = query.Where(c => c != item);
item = 'i';
query = query.Where(c => c != item);
item = 'o';
query = query.Where(c => c != item);
item = 'u';
query = query.Where(c => c != item);

foreach (char c in query)
    Console.Write(c); // 只刪除了'u'----Not what yo might expect

2. 整理Linq to Objects中運運算元延遲計算特性

按字母順序整理:

1、具有延遲計算的運運算元

Cast,Concat,DefaultIfEmpty,Distinct,Except,GroupBy,GroupJoin,Intersect,Join,OfType,OrderBy,OrderByDescending ,Repeat,Reverse,Select,SelectMany,Skip,SkipWhile,Take,TakeWhile,ThenBy,ThenByDescending,Union,Where,Zip

2、立即執行的運運算元

Aggregate,All,Any,Average,Contains,Count,ElementAt,ElementAtOrDefault,Empty,First,FirstOrDefault,Last,LastOrDefault ,LongCount,Max,Min,Range,SequenceEqual,Single,SingleOrDefault,Sum,ToArray,ToDictionary,ToList,ToLookup

注意:特殊的AsEnumerable運運算元,用於處理LINQ to Entities操作遠端資料來源,將IQueryable遠端資料立即轉化為原生的IEnumerable集合。若AsEnumerable接收引數是IEnumerable記憶體集合則什麼都不做。

三、LINQ 和字串

LINQ 可用於查詢和轉換字串和字串集合。它對文字檔案中的半結構化資料尤其有用。LINQ 查詢可與傳統的字串函數和正規表示式結合使用。

例如:

  • 可以使用 Split 或 Split 方法來建立字串陣列,然後可以使用 LINQ 來查詢或修改此陣列;
  • 可以在 LINQ 查詢的 where 子句中使用 IsMatch 方法;
  • 可以使用 LINQ 來查詢或修改由正規表示式返回的 MatchCollection 結果

1、查詢文字塊

範例1: 統計單詞在字串中出現的次數

請注意,若要執行計數,請先呼叫 Split 方法來建立詞陣列。Split 方法存在效能開銷。如果對字串執行的唯一操作是計數詞,則應考慮改用 Matches 或 IndexOf 方法。

string text = @"Historically, the world of data and the world of objects" +
          @" have not been well integrated. ";

string searchTerm = "data";

//字串轉換成陣列
string[] source = text.Split(new char[] { '.', '?', '!', ' ', ';', ':', ',' }, StringSplitOptions.RemoveEmptyEntries);

// 建立查詢,並忽略大小寫比較 
var matchQuery = from word in source
                 where word.ToLowerInvariant() == searchTerm.ToLowerInvariant()
                 select word;

// 統計匹配數量.  
int wordCount = matchQuery.Count();
Console.WriteLine("{0} occurrences(s) of the search term "{1}" were found.", wordCount, searchTerm);
//1 occurrences(s) of the search term "data" were found.

範例2: 在文字檔案中,找出包含一組指定單詞的的句子。

在此範例中,查詢將返回包含單詞“Historically,”、“data,”和“integrated”的句子。 雖然在此範例中搜尋條件陣列是寫死的,但也可以在執行時動態填充此陣列。

string text = @"Historically, the world of data and the world of objects " +
    @"have not been well integrated. Programmers work in C# or Visual Basic " +
    @"and also in SQL or XQuery. On the one side are concepts such as classes, " +
    @"objects, fields, inheritance, and .NET Framework APIs. On the other side " +
    @"are tables, columns, rows, nodes, and separate languages for dealing with " +
    @"them. Data types often require translation between the two worlds; there are " +
    @"different standard functions. Because the object world has no notion of query, a " +
    @"query can only be represented as a string without compile-time type checking or " +
    @"IntelliSense support in the IDE. Transferring data from SQL tables or XML trees to " +
    @"objects in memory is often tedious and error-prone.";

// 將文字塊切割成陣列.  
string[] sentences = text.Split(new char[] { '.', '?', '!' });

// 定義搜尋條件,此列表可以執行時動態新增.  
string[] wordsToMatch = { "Historically", "data", "integrated" };

// 去重,取交集後的數量對比  
var sentenceQuery = from sentence in sentences
                    let w = sentence.Split(new char[] { '.', '?', '!', ' ', ';', ':', ',' }, StringSplitOptions.RemoveEmptyEntries)
                    where w.Distinct().Intersect(wordsToMatch).Count() == wordsToMatch.Count()
                    select sentence;

// 執行這個查詢 
foreach (string str in sentenceQuery)
{
    Console.WriteLine(str);
}
//Historically, the world of data and the world of objects have not been well integrated

查詢執行時首先將文字拆分成句子,然後將句子拆分成包含每個單詞的字串陣列。對於每個這樣的陣列,Distinct 方法移除所有重複的單詞,然後查詢對單詞陣列和 wordstoMatch 陣列執行 Intersect 操作。如果交集的計數與 wordsToMatch 陣列的計數相同,則在單詞中找到了所有的單詞,且返回原始句子。

在對 Split 的呼叫中,使用標點符號作為分隔符,以從字串中移除標點符號。如果您沒有這樣做,則假如您有一個字串“Historically,”,該字串不會與 wordsToMatch 陣列中的“Historically”相匹配。根據源文字中標點的型別,您可能必須使用其他分隔符。

範例3: 查詢字串中的字元

因為 String 類實現泛型 IEnumerable 介面,所以可以將任何字串作為字元序列進行查詢。但是,這不是 LINQ 的常見用法。若要執行復雜的模式匹配操作,請使用 Regex 類。

以下範例查詢一個字串以確定它所包含的數位數量。 請注意,在第一次執行此查詢後將“重用”此查詢。 這是可能的,因為查詢本身並不儲存任何實際的結果。

string aString = "ABCDE99F-J74-12-89A";

// 只選擇數位的字元  
IEnumerable<char> stringQuery =
  from ch in aString
  where Char.IsDigit(ch)
  select ch;

// 執行這個查詢  
foreach (char c in stringQuery)
    Console.Write(c + " ");
//9 9 7 4 1 2 8 9 

// 對上面的查詢呼叫Count方法  
int count = stringQuery.Count();
Console.WriteLine("Count = {0}", count);
//Count = 8

// 選擇第一個「-」之前的所有字元 
IEnumerable<char> stringQuery2 = aString.TakeWhile(c => c != '-');

// 執行第二個查詢
foreach (char c in stringQuery2)
    Console.Write(c);
//ABCDE99F

範例4:用正規表示式結合 LINQ 查詢

此範例演示如何使用 Regex 類建立正規表示式以便在文字字串中進行更復雜的匹配。使用 LINQ 查詢可以方便地對您要用正規表示式搜尋的檔案進行準確篩選,以及對結果進行加工。

//根據不同版本的 vs 修改路徑
string startFolder = @"C:Program Files (x86)Microsoft Visual Studio 11.0";
IEnumerable fileList = Directory.GetFiles(startFolder, "*.*", SearchOption.AllDirectories).Select(p => new System.IO.FileInfo(p));
//建立正規表示式來尋找所有的"Visual"
Regex searchTerm = new Regex(@"Visual (Basic|C#|C++|Studio)");

//搜尋每一個「.htm」檔案
//通過 where 找到匹配項
//注意:select 中的變數要求顯示宣告其型別,因為 MatchCollection 不是泛型 IEnumerable 集合
var queryMatchingFiles =
    from file in fileList
    where file.Extension == ".htm"
    let fileText = File.ReadAllText(file.FullName)
    let matches = searchTerm.Matches(fileText)
    where matches.Count > 0
    select new
    {
        name = file.FullName,
        matchedValues = from Match match in matches
                        select match.Value
    };

foreach (var v in queryMatchingFiles)
{
    //修剪匹配找到的檔案中的路徑
    string s = v.name.Substring(startFolder.Length - 1);
    Console.WriteLine(s);

    //輸出找到的匹配值
    foreach (var v2 in v.matchedValues)
    {
        Console.WriteLine("  " + v2);
    }
}

//professional2052License.htm
//  Visual Studio
//  Visual Studio
//   Visual Studio
//VBVBWizardsFrameSetTemplates1033BanToc.htm
//   Visual Studio

還可以查詢由 RegEx 搜尋返回的 MatchCollection 物件。在此範例中,結果中僅生成每個匹配項的值。但也可使用 LINQ 對該集合執行各種篩選、排序和分組操作。

【注意】由於 MatchCollection 是非泛型 IEnumerable 集合,因此必須顯式宣告查詢中的範圍變數的型別。

2、查詢文字格式的半結構化資料

許多不同型別的文字檔案都包含一系列行,通常具有類似的格式設定,例如製表符分隔或逗號分隔的檔案或固定長度的行。

將此類文字檔案讀入記憶體後,可以使用 LINQ 來查詢和/或修改其中的行。 LINQ 查詢還簡化了合併來自多個源的資料的任務。

範例1、如何查詢兩個集合間的差異

此範例演示如何使用 LINQ 對兩個字串列表進行比較,並輸出那些位於 names1.txt 中但不在 names2.txt 中的行。

names1.txt

Bankov, Peter 
Holm, Michael 
Garcia, Hugo 
Potra, Cristina 
Noriega, Fabricio 
Aw, Kam Foo 
Beebe, Ann 
Toyoshima, Tim 
Guy, Wey Yuan 
Garcia, Debra

names2.txt

Liu, Jinghao 
Bankov, Peter 
Holm, Michael 
Garcia, Hugo 
Beebe, Ann 
Gilchrist, Beth 
Myrcha, Jacek 
Giakoumakis, Leo 
McLin, Nkenge 
El Yassir, Mehdi

程式碼:

//建立資料來源
var names1Text = File.ReadAllLines(@"names1.txt");
var names2Text = File.ReadAllLines(@"names2.txt");

//建立查詢,這裡必須使用方法語法
var query = names1Text.Except(names2Text);

//執行查詢
Console.WriteLine("The following lines are in names1.txt but not names2.txt");
foreach (var name in query)
{
    Console.WriteLine(name);
}

【注意】某些型別的查詢操作(如 ExceptDistinctUnion 和 Concat)只能用基於方法的語法表示。

範例2: 根據某欄位進行排序或過濾文字行

下面的範例演示如何按結構化文字(如逗號分隔值)行中的任意欄位對該文字行進行排序。可在執行時動態指定該欄位。此範例還演示如何從方法返回查詢變數。

scores.csv:假定 scores.csv 中的欄位表示學生的 ID 號,後面跟四個測驗分數。

111, 97, 92, 81, 60 
112, 75, 84, 91, 39 
113, 88, 94, 65, 91 
114, 97, 89, 85, 82 
115, 35, 72, 91, 70 
116, 99, 86, 90, 94 
117, 93, 92, 80, 87 
118, 92, 90, 83, 78 
119, 68, 79, 88, 92 
120, 99, 82, 81, 79 
121, 96, 85, 91, 60 
122, 94, 92, 91, 91

// 建立資料來源
string[] scores = System.IO.File.ReadAllLines(@"scores.csv");

// 可以改為 0~4 的任意值 
int sortField = 1;

Console.WriteLine("Sorted highest to lowest by field [{0}]:", sortField);

//演示從方法返回查詢,這裡執行查詢
foreach (string str in RunQuery(scores, sortField))
{
    Console.WriteLine(str);
}

// Keep the console window open in debug mode.  
Console.WriteLine("Press any key to exit");

// 返回查詢變數,非查詢結果
static IEnumerable<string> RunQuery(IEnumerable<string> source, int num)
{
    // 分割字串來排序  
    var scoreQuery = from line in source
                     let fields = line.Split(',')
                     orderby fields[num] descending
                     select line;

    return scoreQuery;
}

/* Output (if sortField == 1):  
   Sorted highest to lowest by field [1]:  
    116, 99, 86, 90, 94  
    120, 99, 82, 81, 79  
    111, 97, 92, 81, 60  
    114, 97, 89, 85, 82  
    121, 96, 85, 91, 60  
    122, 94, 92, 91, 91  
    117, 93, 92, 80, 87  
    118, 92, 90, 83, 78  
    113, 88, 94, 65, 91  
    112, 75, 84, 91, 39  
    119, 68, 79, 88, 92  
    115, 35, 72, 91, 70  
 */

範例3、如何對一個分割的檔案的欄位重新排序

逗號分隔值 (CSV) 檔案是一種文字檔案,通常用於儲存電子試算表資料或其他由行和列表示的表格資料。通過使用 Split 方法分隔欄位,可以非常輕鬆地使用 LINQ 來查詢和操作 CSV 檔案。事實上,可以使用此技術來重新排列任何結構化文字行部分;此技術不侷限於 CSV 檔案。

在下面的範例中,假定有三列分別代表學生的“姓氏”、“名字”和“ID”。這些欄位基於學生的姓氏按字母順序排列。查詢生成一個新序列,其中首先出現的是 ID 列,後面的第二列組合了學生的名字和姓氏。根據 ID 欄位重新排列各行。結果儲存到新檔案,但不修改原始資料。

spreadsheet1.csv

Adams,Terry,120 
Fakhouri,Fadi,116 
Feng,Hanying,117 
Garcia,Cesar,114 
Garcia,Debra,115 
Garcia,Hugo,118 
Mortensen,Sven,113 
O'Donnell,Claire,112 
Omelchenko,Svetlana,111 
Tucker,Lance,119 
Tucker,Michael,122 
Zabokritski,Eugene,121

程式碼

//資料來源
var lines = File.ReadAllLines(@"spreadsheet1.csv");
//將舊資料的第2列的欄位放到第一位,逆向結合第0列和第1列的欄位
var query = from line in lines
            let t = line.Split(',')
            orderby t[2]
            select $"{t[2]}, {t[1]} {t[0]}";

foreach (var q in query)
{
    Console.WriteLine(q);
}

//寫入檔案
File.WriteAllLines("spreadsheet2.csv", query);

範例4、如何組合和比較字串集合

此範例演示如何合併包含文字行的檔案,然後排序結果。具體來說,此範例演示如何對兩組文字行執行簡單的串聯、聯合和交集。

names1.txt

Bankov, Peter 
Holm, Michael 
Garcia, Hugo 
Potra, Cristina 
Noriega, Fabricio 
Aw, Kam Foo 
Beebe, Ann 
Toyoshima, Tim 
Guy, Wey Yuan 
Garcia, Debra

names2.txt

Liu, Jinghao 
Bankov, Peter 
Holm, Michael 
Garcia, Hugo 
Beebe, Ann 
Gilchrist, Beth 
Myrcha, Jacek 
Giakoumakis, Leo 
McLin, Nkenge 
El Yassir, Mehdi

程式碼:

var names1Text = File.ReadAllLines(@"names1.txt");
var names2Text = File.ReadAllLines(@"names2.txt");

//簡單連線,並排序。重複儲存。 
var concatQuery = names1Text.Concat(names2Text).OrderBy(x => x);
OutputQueryResult(concatQuery, "Simple concatenate and sort. Duplicates are preserved:");

//基於預設字串比較器連線,並刪除重複。 
var unionQuery = names1Text.Union(names2Text).OrderBy(x => x);
OutputQueryResult(unionQuery, "Union removes duplicate names:");

//查詢在兩個檔案中出現的名稱 
var intersectQuery = names1Text.Intersect(names2Text).OrderBy(x => x);
OutputQueryResult(intersectQuery, "Merge based on intersect:");

//在每個列表中找到匹配的欄位。使用 concat 將兩個結果合併,然後使用預設的字串比較器進行排序
const string nameMatch = "Garcia";
var matchQuery1 = from name in names1Text
                  let t = name.Split(',')
                  where t[0] == nameMatch
                  select name;
var matchQuery2 = from name in names2Text
                  let t = name.Split(',')
                  where t[0] == nameMatch
                  select name;

var temp = matchQuery1.Concat(matchQuery2).OrderBy(x => x);
OutputQueryResult(temp, $"Concat based on partial name match "{nameMatch}":");


private static void OutputQueryResult(IEnumerable<string> querys, string title)
{
    Console.WriteLine(Environment.NewLine + title);
    foreach (var query in querys)
    {
        Console.WriteLine(query);
    }

    Console.WriteLine($"{querys.Count()} total names in list");
}

範例5:從多個源填充物件集合

不要嘗試將記憶體中的資料或檔案系統中的資料與仍在資料庫中的資料相聯接。此種跨域聯接會生成未定義的結果,因為資料庫查詢和其他型別的源定義聯接運算的方式可能不同。另外,如果資料庫中的資料量足夠大,則存在此類運算引發記憶體不足異常的風險。

若要將資料庫資料與記憶體中的資料相聯接,請首先對資料庫查詢呼叫 ToList 或 ToArray,然後對返回的集合執行聯接。

//每行 names.csv 包含姓氏,名字,和身份證號,以逗號分隔。例如,Omelchenko,Svetlana,111
var names = File.ReadAllLines(@"names.csv");
//每行 scores.csv 包括身份證號碼和四個測試評分,以逗號分隔。例如,111,97,92,81,60 
var scores = File.ReadAllLines(@"scores.csv");

//使用一個匿名的型別合併資料來源。
//【注意】動態建立一個 int 的考試成績成員列表。
//跳過分割字串中的第一項,因為它是學生的身份證,不是一個考試成績
var students = from name in names
               let t = name.Split(',')
               from score in scores

               let t2 = score.Split(',')
               where t[2] == t2[0]
               select new
               {
                   FirstName = t[0],
                   LastName = t[1],
                   ID = Convert.ToInt32(t[2]),
                   ExamScores = (from scoreAsText in t2.Skip(1)
                                 select Convert.ToInt32(scoreAsText)).ToList()
               };
//顯示每個學生的名字和考試平均成績。
foreach (var student in students)
{
    Console.WriteLine(
        $"The average score of {student.FirstName} {student.LastName} is {student.ExamScores.Average()}.");
}

範例6、如何向不同的檔案中加入內容

此範例演示如何聯接兩個逗號分隔檔案中的資料,這兩個檔案共用一個用作匹配鍵的共同值。如果您必須將兩個電子試算表的資料或一個電子試算表和一個其他格式的檔案的資料組合為一個新檔案,則此技術很有用。還可以修改此範例以適合任意種類的結構化文字。

names.csv:此檔案表示一個電子試算表。該電子試算表包含學生的姓氏、名字和學生 ID。

Omelchenko,Svetlana,111 
O'Donnell,Claire,112 
Mortensen,Sven,113 
Garcia,Cesar,114 
Garcia,Debra,115 
Fakhouri,Fadi,116 
Feng,Hanying,117 
Garcia,Hugo,118 
Tucker,Lance,119 
Adams,Terry,120 
Zabokritski,Eugene,121 
Tucker,Michael,122

scores.csv:此檔案表示電子試算表資料。第 1 列是學生的 ID,第 2 至 5 列是測驗分數。

111, 97, 92, 81, 60 
112, 75, 84, 91, 39 
113, 88, 94, 65, 91 
114, 97, 89, 85, 82 
115, 35, 72, 91, 70 
116, 99, 86, 90, 94 
117, 93, 92, 80, 87 
118, 92, 90, 83, 78 
119, 68, 79, 88, 92 
120, 99, 82, 81, 79 
121, 96, 85, 91, 60 
122, 94, 92, 91, 91

程式碼:

var names = File.ReadAllLines(@"names.csv");
var scores = File.ReadAllLines(@"scores.csv");

//Name:    Last[0],       First[1],  ID[2]
//          Omelchenko,    Svetlana,  11
//Score:   StudentID[0],  Exam1[1]   Exam2[2],  Exam3[3],  Exam4[4]
//          111,           97,        92,        81,        60

//該查詢基於 id 連線兩個不同的電子試算表
var query = from name in names
            let t1 = name.Split(',')
            from score in scores
            let t2 = score.Split(',')
            where t1[2] == t2[0]
            orderby t1[0]
            select $"{t1[0]},{t2[1]},{t2[2]},{t2[3]},{t2[4]}";

//輸出
OutputQueryResult(query, "Merge two spreadsheets:");

private static void OutputQueryResult(IEnumerable<string> querys, string title)
{
    Console.WriteLine(Environment.NewLine + title);
    foreach (var query in querys)
    {
        Console.WriteLine(query);
    }

    Console.WriteLine($"{querys.Count()} total names in list");
}

範例7:如何使用 group 將一個檔案拆分成多個檔案

此範例演示一種進行以下操作的方法:合併兩個檔案的內容,然後建立一組以新方式組織資料的新檔案。

name1.txt

Bankov, Peter 
Holm, Michael 
Garcia, Hugo 
Potra, Cristina 
Noriega, Fabricio 
Aw, Kam Foo 
Beebe, Ann 
Toyoshima, Tim 
Guy, Wey Yuan 
Garcia, Debra 
name2.text 
Liu, Jinghao 
Bankov, Peter 
Holm, Michael 
Garcia, Hugo 
Beebe, Ann 
Gilchrist, Beth 
Myrcha, Jacek 
Giakoumakis, Leo 
McLin, Nkenge 
El Yassir, Mehdi

name2.txt

Liu, Jinghao 
Bankov, Peter 
Holm, Michael 
Garcia, Hugo 
Beebe, Ann 
Gilchrist, Beth 
Myrcha, Jacek 
Giakoumakis, Leo 
McLin, Nkenge 
El Yassir, Mehdi

程式碼:

var fileA = File.ReadAllLines(@"names1.txt");
var fileB = File.ReadAllLines(@"names2.txt");

//並集:連線並刪除重複的名字
var mergeQuery = fileA.Union(fileB);
//根據姓氏的首字母對姓名進行分組
var query = from name in mergeQuery
            let t = name.Split(',')
            group name by t[0][0] into g
            orderby g.Key
            select g;

//為建立的每個組建立一個新檔案,請注意,需要使用巢狀的foreach迴圈來存取每個組的單個項。 
foreach (var g in query)
{
    var fileName = @"testFile_" + g.Key + ".txt";
    Console.WriteLine(g.Key + ":");

    //寫入檔案
    using (var sw = new StreamWriter(fileName))
    {
        foreach (var name in g)
        {
            sw.WriteLine(name);
            Console.WriteLine(" " + name);
        }
    }
}

對於與資料檔案位於同一資料夾中的每個組,程式將為這些組編寫單獨的檔案。

範例8、如何計算一個 CSV 文字檔案中的列值

此範例演示如何對 .csv 檔案的列執行諸如 Sum、Average、Min 和 Max 等聚合計算。此處所示的範例原則可以應用於其他型別的結構化文字。

scores.csv

111, 97, 92, 81, 60 
112, 75, 84, 91, 39 
113, 88, 94, 65, 91 
114, 97, 89, 85, 82 
115, 35, 72, 91, 70 
116, 99, 86, 90, 94 
117, 93, 92, 80, 87 
118, 92, 90, 83, 78 
119, 68, 79, 88, 92 
120, 99, 82, 81, 79 
121, 96, 85, 91, 60 
122, 94, 92, 91, 91

scores.csv:假定第一列表示學員 ID,後面幾列表示四次考試的分數。

var scores = File.ReadAllLines(@"scores.csv");

//指定要計算的列
const int examNum = 3;

//scores.csv 格式:
//Student ID    Exam#1  Exam#2  Exam#3  Exam#4
//111,          97,     92,     81,     60

//+1 表示跳過第一列
//計算單一列
SingleColumn(scores, examNum + 1);
Console.WriteLine();

//計算多列
MultiColumns(scores);

private static void SingleColumn(IEnumerable<string> strs, int examNum)
{
    Console.WriteLine("Single Column Query:");

    //查詢分兩步:
    // 1.分割字串
    // 2.對要計算的列的值轉換為 int
    var query = from str in strs
                let t = str.Split(',')
                select Convert.ToInt32(t[examNum]);

    //對指定的列進行統計
    var average = query.Average();
    var max = query.Max();
    var min = query.Min();

    Console.WriteLine($"Exam #{examNum}: Average:{average:##.##} High Score:{max} Low Score:{min}");
}

private static void MultiColumns(IEnumerable<string> strs)
{
    Console.WriteLine("Multi Column Query:");

    //查詢步驟:
    // 1.分割字串
    // 2.跳過 id 列(第一列)
    // 3.將當前行的每個評分都轉換成 int,並選擇整個序列作為一行結果。 
    var query = from str in strs
                let t1 = str.Split(',')
                let t2 = t1.Skip(1)
                select (from t in t2
                        select Convert.ToInt32(t));

    //執行查詢並快取結果以提高效能
    var results = query.ToList();
    //找出結果的列數
    var count = results[0].Count();

    //執行統計
    //為每一列分數的迴圈執行一次迴圈
    for (var i = 0; i < count; i++)
    {
        var query2 = from result in results
                     select result.ElementAt(i);

        var average = query2.Average();
        var max = query2.Max();
        var min = query2.Min();

        //+1 因為 #1 表示第一次考試
        Console.WriteLine($"Exam #{i + 1} Average: {average:##.##} High Score: {max} Low Score: {min}");
    }

}

查詢的工作原理是使用 Split 方法將每一行文字轉換為陣列。每個陣列元素表示一列。最後,每一列中的文字都轉換為其數位表示形式。如果檔案是製表符分隔檔案,只需將 Split 方法中的引數更新為 t。

範例9、從 CSV 檔案生成 XML

下面的程式碼對字串陣列執行 LINQ 查詢。

該查詢使用 let 子句將每個字串分隔成欄位陣列。

// Create the text file.  
string csvString = @"GREAL,Great Lakes Food Market,Howard Snyder,Marketing Manager,(503) 555-7555,2732 Baker Blvd.,Eugene,OR,97403,USA  
HUNGC,Hungry Coyote Import Store,Yoshi Latimer,Sales Representative,(503) 555-6874,City Center Plaza 516 Main St.,Elgin,OR,97827,USA  
LAZYK,Lazy K Kountry Store,John Steel,Marketing Manager,(509) 555-7969,12 Orchestra Terrace,Walla Walla,WA,99362,USA  
LETSS,Let's Stop N Shop,Jaime Yorres,Owner,(415) 555-5938,87 Polk St. Suite 5,San Francisco,CA,94117,USA";  
File.WriteAllText("cust.csv", csvString);  
  
// Read into an array of strings.  
string[] source = File.ReadAllLines("cust.csv");  
XElement cust = new XElement("Root",  
    from str in source  
    let fields = str.Split(',')  
    select new XElement("Customer",  
        new XAttribute("CustomerID", fields[0]),  
        new XElement("CompanyName", fields[1]),  
        new XElement("ContactName", fields[2]),  
        new XElement("ContactTitle", fields[3]),  
        new XElement("Phone", fields[4]),  
        new XElement("FullAddress",  
            new XElement("Address", fields[5]),  
            new XElement("City", fields[6]),  
            new XElement("Region", fields[7]),  
            new XElement("PostalCode", fields[8]),  
            new XElement("Country", fields[9])  
        )  
    )  
);  
Console.WriteLine(cust);

到此這篇關於C#中LINQ to Objects的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com