首頁 > 軟體

C#中的LINQ to Objects詳解(2)

2022-06-06 14:01:25

相關文章:

C#中的LINQ to Objects詳解(1)

C#中的LINQ to Objects詳解(2)

四、Linq和反射

.NET Framework 類庫反射 API 可用於檢查 .NET 程式集中的後設資料,以及建立位於該程式集中的型別、型別成員、引數等等的集合。 因為這些集合支援泛型 IEnumerable 介面,所以可以使用 LINQ 查詢它們。

下面的範例演示瞭如何將 LINQ 與反射配合使用以檢索有關與指定搜尋條件匹配的方法的特定後設資料。 在這種情況下,該查詢將在返回陣列等可列舉型別的程式集中查詢所有方法的名稱。

該範例使用 GetTypes 方法返回指定程式集中的型別的陣列。 將應用 where 篩選器,以便僅返回公共型別。 對於每個公共型別,子查詢使用從 GetMethods 呼叫返回的 MethodInfo 陣列生成。 篩選這些結果,以僅返回其返回型別為陣列或實現 IEnumerable 的其他型別的方法。 最後,通過使用型別名稱作為鍵來對這些結果進行分組。

Assembly assembly = Assembly.Load("System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken= b77a5c561934e089");
var pubTypesQuery = from type in assembly.GetTypes()
                    where type.IsPublic
                    from method in type.GetMethods()
                    where method.ReturnType.IsArray == true  
                                             || (method.ReturnType.GetInterface(typeof(System.Collections.Generic.IEnumerable<>).FullName) != null && method.ReturnType.FullName != "System.String")
                    group method.ToString() by type.ToString();

foreach (var groupOfMethods in pubTypesQuery)
{
    Console.WriteLine("Type: {0}", groupOfMethods.Key);
    foreach (var method in groupOfMethods)
    {
        Console.WriteLine("  {0}", method);
    }
}

Console.WriteLine("Press any key to exit");

五、LINQ 和字串

1、LINQ 和檔案目錄

許多檔案系統操作實質上是查詢,因此非常適合使用 LINQ 方法。

本部分中的查詢是非破壞性查詢。 它們不用於更改原始檔案或資料夾的內容。 這遵循了查詢不應引起任何副作用這條規則。 通常,修改源資料的任何程式碼(包括執行建立/更新/刪除運運算元的查詢)應與只查詢資料的程式碼分開。

範例1、如何查詢具有指定屬性或名稱的檔案

此範例演示如何查詢指定目錄樹中具有指定副檔名(例如“.txt”)的所有檔案,還演示如何根據建立時間返回樹中最新或最舊的檔案。

//該查詢將所有生產的完整路徑。txt檔案指定的資料夾包括子資料夾下。
const string path = @"C:Program Files (x86)Microsoft Visual Studio 14.0";
//取檔案系統快照
var dir = new DirectoryInfo(path);
//該方法假定應用程式在指定路徑下的所有資料夾都具有搜尋許可權。
var files = dir.GetFiles("*.*", SearchOption.AllDirectories);

//建立查詢
var fileQuery = from file in files
                where file.Extension == ".html"
                orderby file.Name
                select file;

//執行查詢
foreach (var file in fileQuery)
{
    Console.WriteLine(file.FullName);
}

//建立和執行一個新的查詢,通過查詢舊檔案的建立時間作為一個出發點
//Last:選最後一個,因為是按日期升序,所以最新的是指最後一個
var newestFile = (from file in fileQuery
                  orderby file.CreationTime
                  select new { file.FullName, file.CreationTime })
                 .Last();

Console.WriteLine($"rnThe newest .txt file is {newestFile.FullName}. Creation time: {newestFile.CreationTime}");

範例2、如何按照擴充套件名對檔案進行分組

此範例演示如何使用 LINQ 對檔案或資料夾列表執行高階分組和排序操作。此外,它還演示如何使用 SkipTake 方法對控制檯視窗中的輸出進行分頁。

下面的查詢演示如何按副檔名對指定目錄樹的內容進行分組。

const string path = @"C:Program Files (x86)Microsoft Visual Studio 14.0Common7";
//「path」的長度,後續用於在輸出時去掉「path」這段字首
var trimLength = path.Length;
//取檔案系統快照
var dir = new DirectoryInfo(path);
//該方法假定應用程式在指定路徑下的所有資料夾都具有搜尋許可權。
var files = dir.GetFiles("*.*", SearchOption.AllDirectories);

//建立查詢
var query = from file in files
            group file by file.Extension.ToLower() into fileGroup
            orderby fileGroup.Key
            select fileGroup;

//一次顯示一組。如果列表實體的行數大於控制檯視窗中的行數,則分頁輸出。 
PageOutput(trimLength, query);

private static void PageOutput(int rootLength, IOrderedEnumerable<string, FileInfo>> query)
{
    //跳出分頁迴圈的標誌
    var isAgain = true;
    //控制檯輸出的高度
    var numLines = Console.WindowHeight - 3;

    //遍歷分組集合
    foreach (var g in query)
    {
        var currentLine = 0;

        do
        {
            Console.Clear();
            Console.WriteLine(string.IsNullOrEmpty(g.Key) ? "[None]" : g.Key);

            //從「currentLine」開始顯示「numLines」條數
            var resultPage = g.Skip(currentLine).Take(numLines);

            //執行查詢
            foreach (var info in resultPage)
            {
                Console.WriteLine("t{0}", info.FullName.Substring(rootLength));
            }

            //記錄輸出行數
            currentLine += numLines;
            Console.WriteLine("點選「任意鍵」繼續,按「End」鍵退出");

            //給使用者選擇是否跳出
            var key = Console.ReadKey().Key;
            if (key != ConsoleKey.End) continue;

            isAgain = false;
            break;
        } while (currentLine < g.Count());

        if (!isAgain)
        {
            break;
        }
    }
}

為了使您可以檢視所有結果,此範例還演示如何按頁檢視結果。這些方法可應用於 Windows 和 Web 應用程式。

請注意,由於程式碼將對組中的項進行分頁,因此需要巢狀的 foreach 迴圈。此外,還會使用某他某個邏輯來計算列表中的當前位置,以及使使用者可以停止分頁並退出程式。在這種特定情況下,將針對原始查詢的快取結果執行分頁查詢。

範例3、如何查詢一組資料夾中的總位元組數

此範例演示如何檢索指定資料夾及其所有子資料夾中的所有檔案所使用的總位元組數。

Sum 方法新增在 select 子句中選擇的所有項的值。您可以輕鬆修改此查詢以檢索指定目錄樹中的最大或最小檔案,方法是呼叫 Min 或 Max 方法,而不是 Sum

const string path = @"C:Program Files (x86)Microsoft Visual Studio 12.0VC#";
var dir = new DirectoryInfo(path);
var files = dir.GetFiles("*.*", SearchOption.AllDirectories);

var query = from file in files
            select file.Length;

//快取結果,以避免多次存取檔案系統
var fileLengths = query as long[] ?? query.ToArray();
//返回最大檔案的大小 
var largestLength = fileLengths.Max();
//返回指定資料夾下的所有檔案中的總位元組數
var totalBytes = fileLengths.Sum();
Console.WriteLine();

Console.WriteLine("There are {0} bytes in {1} files under {2}",totalBytes, files.Count(), path);
Console.WriteLine("The largest files is {0} bytes.", largestLength);

如果您只需要統計特定目錄樹中的位元組數,則可以更高效地實現此目的,而無需建立 LINQ 查詢,因為該查詢會引發建立列表集合作為資料來源的系統開銷。隨著查詢複雜度的增加,或者當您必須對同一資料來源執行多個查詢時,LINQ 方法的有用性也會隨之增加。

範例4、如何比較兩個資料夾中的內容

此範例演示比較兩個檔案列表的三種方法:

  • (1)查詢一個指定兩個檔案列表是否相同的布林值;
  • (2)查詢用於檢索同時位於兩個資料夾中的檔案的交集;
  • (3)查詢用於檢索位於一個資料夾中但不在另一個資料夾中的檔案的差集;
//建立兩個帶比較的資料夾
const string path1 = @"E:Test1";
const string path2 = @"E:Test2";

var dir1 = new DirectoryInfo(path1);
var dir2 = new DirectoryInfo(path2);

//取檔案快照
var files1 = dir1.GetFiles("*.*", SearchOption.AllDirectories);
var files2 = dir2.GetFiles("*.*", SearchOption.AllDirectories);

//自定義檔案比較器
var comparer = new FileComparer();

//該查詢確定兩個資料夾包含相同的檔案列表,基於自定義檔案比較器。查詢立即執行,因為它返回一個bool。 
var areIdentical = files1.SequenceEqual(files2, comparer);
Console.WriteLine(areIdentical == true ? "the two folders are the same" : "The two folders are not the same");

//交集:找相同的檔案 
var queryCommonFiles = files1.Intersect(files2, comparer);

var commonFiles = queryCommonFiles as FileInfo[] ?? queryCommonFiles.ToArray();
if (commonFiles.Any())
{
    Console.WriteLine("The following files are in both folders:");
    foreach (var v in commonFiles)
    {
        Console.WriteLine(v.FullName);
    }
}
else
{
    Console.WriteLine("There are no common files in the two folders.");
}

//差集:對比兩個資料夾的差異
var diffQuery = files1.Except(files2, comparer);

Console.WriteLine("The following files are in list1 but not list2:");
foreach (var v in diffQuery)
{
    Console.WriteLine(v.FullName);
}

//該實現定義了一個非常簡單的兩個 FileInfo 物件之間的比較。它只比較檔案的名稱和它們位元組數的長度
public class FileComparer : IEqualityComparer
{
    public bool Equals(FileInfo x, FileInfo y)
    {
        return string.Equals(x.Name, y.Name, StringComparison.CurrentCultureIgnoreCase) && x.Length == y.Length;
    }

    //返回一個比較標準的雜湊值。根據 IEqualityComparer 規則,如果相等,那麼雜湊值也必須是相等的。
    //因為這裡所定義的相等只是一個簡單的值相等,而不是參照標識,所以兩個或多個物件將產生相同的雜湊值是可能的。 
    public int GetHashCode(FileInfo obj)
    {
        var s = string.Format("{0}{1}", obj.Name, obj.Length);

        return s.GetHashCode();
    }
}

【注意】 可以修改上述這些方法以便對任意型別的物件序列進行比較。

此處顯示的 FileComparer 類演示如何將自定義比較器類與標準查詢運運算元一起使用。該類不是為在實際方案中使用而設計的。它只是使用每個檔案的名稱和長度(以位元組為單位)來確定每個資料夾的內容是否相同。在實際方案中,應對此比較器進行修改以執行更嚴格的相等性檢查。

範例5、如何在目錄樹中查詢最大的檔案

此範例演示與檔案大小(以位元組為單位)相關的五種查詢:

  • 如何檢索最大檔案的大小(以位元組為單位);
  • 如何檢索最小檔案的大小(以位元組為單位);
  • 如何從指定的根資料夾下的一個或多個資料夾檢索 FileInfo 物件最大或最小檔案;
  • 如何檢索一個序列,如 10 個最大檔案。

下面的範例包含五種不同的查詢,這些查詢演示如何根據檔案大小(以位元組為單位)查詢和分組檔案。可以輕鬆地修改這些範例,以使查詢基於 FileInfo物件的某個其他屬性。

const string path = @"C:Program Files (x86)Microsoft Visual Studio 12.0VC#";
var dir = new DirectoryInfo(path);
var files = dir.GetFiles("*.*", SearchOption.AllDirectories);

var query = from file in files
            select file.Length;

//返回最大檔案的大小
var maxSize = query.Max();
Console.WriteLine("The length of the largest file under {0} is {1}",path, maxSize);

//倒序排列
var query2 = from file in files
             let len = file.Length
             where len > 0
             orderby len descending
             select file;

var fileInfos = query2 as FileInfo[] ?? query2.ToArray();
//倒序排列的第一個就是最大的檔案
var longestFile = fileInfos.First();
//倒序排列的第一個就是最小的檔案
var smallestFile = fileInfos.Last();

Console.WriteLine("The largest file under {0} is {1} with a length of {2} bytes", path, longestFile.FullName, longestFile.Length);
Console.WriteLine("The smallest file under {0} is {1} with a length of {2} bytes", path, smallestFile.FullName, smallestFile.Length);
Console.WriteLine("===== The 10 largest files under {0} are: =====", path);

//返回前10個最大的檔案
var queryTenLargest = fileInfos.Take(10);
foreach (var v in queryTenLargest)
{
    Console.WriteLine("{0}: {1} bytes", v.FullName, v.Length);
}

若要返回一個或多個完整的 FileInfo 物件,查詢必須首先檢查資料來源中的每個物件,然後按這些物件的 Length 屬性的值排序它們。然後查詢可以返回具有最大長度的單個物件或序列。使用 First 可返回列表中的第一個元素。使用 Take 可返回前 n 個元素。指定降序排序順序可將最小的元素放在列表的開頭。

範例6、如何在目錄樹中查詢重複的檔案

有時,多個資料夾中可能存在同名的檔案。例如,在 Visual Studio 安裝資料夾中,有多個資料夾包含 readme.htm 檔案。

此範例演示如何在指定的根資料夾中查詢這樣的重複檔名。

第二個範例演示如何查詢其大小和建立時間也匹配的檔案。

static void Main(string[] args)
{
    QueryDuplicates();
    //QueryDuplicates2();

    Console.ReadKey();
}

static void QueryDuplicates()
{
    const string path = @"C:Program Files (x86)Microsoft Visual Studio 12.0";
    var dir = new DirectoryInfo(path);
    var files = dir.GetFiles("*.*", SearchOption.AllDirectories);
    var charsToSkip = path.Length;

    var queryDupNames = (from file in files
                         group file.FullName.Substring(charsToSkip) by file.Name into fileGroup
                         where fileGroup.Count() > 1
                         select fileGroup).Distinct();

    PageOutput<string, string>(queryDupNames);
}

private static void QueryDuplicates2()
{
    const string path = @"C:Program Files (x86)Microsoft Visual Studio 12.0";
    var dir = new DirectoryInfo(path);
    var files = dir.GetFiles("*.*", SearchOption.AllDirectories);
    //路徑的長度
    var charsToSkip = path.Length;

    //注意一個複合鍵的使用。三個屬性都匹配的檔案屬於同一組。
    //匿名型別也可以用於複合鍵,但不能跨越方法邊界。 
    var queryDupFiles = from file in files
                        group file.FullName.Substring(charsToSkip) by
                            new PortableKey() { Name = file.Name, CreationTime = file.CreationTime, Length = file.Length }
                            into fileGroup
                        where fileGroup.Count() > 1
                        select fileGroup;

    var queryDupNames = queryDupFiles as IGroupingstring>[] ?? queryDupFiles.ToArray();
    var list = queryDupNames.ToList();
    var i = queryDupNames.Count();

    //分頁輸出
    PageOutputstring>(queryDupNames);

}

private static void PageOutput(IEnumerable> queryDupNames)
{
    //跳出分頁迴圈的標誌 
    var isAgain = true;
    var numLines = Console.WindowHeight - 3;

    var dupNames = queryDupNames as IGrouping[] ?? queryDupNames.ToArray();
    foreach (var queryDupName in dupNames)
    {
        //分頁開始
        var currentLine = 0;

        do
        {
            Console.Clear();
            Console.WriteLine("Filename = {0}", queryDupName.Key.ToString() == string.Empty ? "[none]" : queryDupName.Key.ToString());

            //跳過 currentLine 行,取 numLines 行
            var resultPage = queryDupName.Skip(currentLine).Take(numLines);

            foreach (var fileName in resultPage)
            {
                Console.WriteLine("t{0}", fileName);
            }

            //增量器記錄已顯示的行數
            currentLine += numLines;

            //讓使用者自動選擇下一下
            //Console.WriteLine("Press any key to continue or the 'End' key to break...");
            //var key = Console.ReadKey().Key;
            //if (key == ConsoleKey.End)
            //{
            //    isAgain = false;
            //    break;
            //}

            //按得有點累,還是讓它自動下一頁吧
            Thread.Sleep(100);

        } while (currentLine < queryDupName.Count());

        //if (!isAgain)
        //    break;
    }
}

第一個查詢使用一個簡單的鍵確定是否匹配;這會找到同名但內容可能不同的檔案。第二個查詢使用複合鍵並根據 FileInfo 物件的三個屬性來確定是否匹配。此查詢非常類似於查詢同名且內容類似或相同的檔案。

範例7、如何在資料夾中查詢檔案的內容

此範例演示如何查詢指定目錄樹中的所有檔案、開啟每個檔案並檢查其內容。 此類技術可用於對目錄樹的內容建立索引或反向索引。 此範例中執行的是簡單的字串搜尋。 但是,可使用正規表示式執行更復雜型別的模式匹配。

const string path = @"C:Program Files (x86)Microsoft Visual Studio 12.0";
var dir = new DirectoryInfo(path);
var files = dir.GetFiles("*.*", SearchOption.AllDirectories);

//待匹配的字串
const string searchTerm = @"Visual Studio";
//搜尋每個檔案的內容。
//您也可以使用正規表示式替換 Contains 方法
var queryMatchingFiles = from file in files

                         where file.Extension == ".html"
                         let content = GetFileConetnt(file.FullName)
                         where content.Contains(searchTerm)
                         select file.FullName;

//執行查詢
Console.WriteLine("The term "{0}" was found in:", searchTerm);
foreach (var filename in queryMatchingFiles)
{
    Console.WriteLine(filename);
}
/// 

/// 讀取檔案的所有內容
/// 
/// 
/// 
static string GetFileConetnt(string fileName)
{
    //如果我們在快照後已刪除該檔案,則忽略它,並返回空字串。 
    return File.Exists(fileName) ? File.ReadAllText(fileName) : "";
}

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


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