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

相關文章:

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($"\r\nThe newest .txt file is {newestFile.FullName}. Creation time: {newestFile.CreationTime}");

實例2、如何按照擴展名對文件進行分組

此示例演示如何使用 LINQ 對文件或文件夾列表執行高級分組和排序操作。此外,它還演示如何使用 Skip 和 Take 方法對控制臺窗口中的輸出進行分頁。

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

const string path = @"C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7";
//“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.0\VC#";
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.0\VC#";
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的文章就介紹到這瞭。希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。

推薦閱讀: