C# 基礎知識系列- 17 實戰篇 編寫一個小工具(1)

0. 前言

這是對C# 基礎系列的一個總結,現在我們利用之前學到的知識做一個小小的工具來給我們使用。

如果有看過IO篇的小夥伴,應該有印象。當時我提過一個場景描述,我們在平時使用系統的時候,經常會為了找某個文件的位置而煩惱。那麼我們現在嘗試寫一個控制台程式來幫助我們找文件的具體位置。

1. 分析

好,大家應該初步了解了需求內容。然後讓我們來做一個簡單的需求分析:

  1. 簡單分析一下需求包括哪些功能點
  2. 規劃各個功能點的實現方式

嗯,理論上講還有一大堆的步驟,但因為是個練手的小項目就不扯那麼多沒用的了。簡單來講就是,分兩步:

  1. 抓取系統可以訪問的所有文件,並保存其全路徑
  2. 根據輸入的參數查詢文件的全路徑

需求分析完了,然後尋找可以實現的技術,我們現有的技術有IO、文件/路徑操作、任務模式等技術,那麼可以供我們選擇的技術一目了然了:通過文件/目錄/路徑API訪問所有的文件目錄,使用字典保存,然後使用Linq查詢文件所在目錄。

OK,需求分析完了,技術也確認了。那麼我們現在開始吧,小夥伴們跟緊了哦,車速不快的。

2. 開始

這裡簡單演示一下如何用Rider和VSCode、Visual Studio2019創建項目。

2.1. 創建一個名為 FileFinder的項目

a.使用Rider:

點擊箭頭所指方向:

image-20200506213821025

先在左側選擇Console Application,然後修改 Project name,最後修改 Solution Directory為自己的目錄:

image-20200506214124769

然後點擊 Create,創建完成結果如下:

image-20200506214423649

Rider創建項目的步驟在Windows、Linux、Mac三個系統都是一樣的。

b. 使用VS Code創建項目

使用VS Code創建項目與Rider和Visual Studio有所不同,步驟比較繁瑣:

先在合適的文件夾下創建一個fileFinder目錄,並在fileFinder目錄下打開命令行,輸入以下命令:

dotnet new sln -n fileFinder # 創建一個名為 fileFinder 的解決方案
dotnet new console -n fileFinder # 創建一個名為 fileFinder的控制台程式
dotnet sln add fileFinder # 把 fileFinder的項目添加到fileFinder的解決方案里

最終結果應該是這樣的:

image-20200506220949536

c.使用 Visual Studio

file

選擇【創建新項目】
file
注意框住地方的選擇,選控制台程式,然後點擊下一步
file
填寫項目名稱、路徑,點擊創建
file

2.2 開始編寫程式

現在我們創建完成了一個項目,然後可以開始編寫我們的程式了。

首先創建一個遍歷所有目錄的方法:

public static Dictionary<string,List<string>> OverDirectories()
{
    //
    return null;
}

現在我們有一個問題,因為Windows的特殊性,目錄結構分為了磁碟:\文件夾這種形式,我們沒法通過設置一個根目錄去遍歷,這時候就要藉助一下官方文檔了。通過查閱API,我們發現一個類:

public sealed class DriveInfo : System.Runtime.Serialization.ISerializable//提供對有關驅動器的資訊的訪問。

有一個方法:

public static System.IO.DriveInfo[] GetDrives ();// 檢索電腦上的所有邏輯驅動器的驅動器名稱。

再看一下屬性:

public string Name { get; }// 獲取驅動器的名稱,如 C:\。
public System.IO.DirectoryInfo RootDirectory { get; }// 獲取驅動器的根目錄。

初步查看滿足我們的需要,先在Program.cs的頭添加命名空間引用:

using System.IO;

表示在這個程式碼文件中會使用這個命名空間的類或者結構體等元素。

在項目中編寫一個方法:

public static void GetDrivers()
{
    var drives = DriveInfo.GetDrives();
    foreach(var drive in drives)
    {
        Console.WriteLine($"驅動器名稱:{drive.Name}:\t {drive.RootDirectory}");
    }
}

然後修改Main方法為:

static void Main(string[] args)
{
    GetDrivers();
}

運行程式,下圖是Linux系統的列印結果:(Rider/Visual Studio的運行程式快捷鍵是F5)

image-20200506224209939

經過完美符合我們的需求,修改GetDrivers方法,使其可以返回所有驅動器的根目錄:

先引入以下命名空間的引用:

using System.Linq;// Linq的支援
using System.Collections.Generic;//泛型集合的支援

修改方法如下:

public static List<DirectoryInfo> GetDrivers()
{
    var drives = DriveInfo.GetDrives();
    return drives.Select(p=>p.RootDirectory).ToList();
}

然後回到方法OverDirectories里,先獲取所有的驅動器,遍歷所有驅動器下的所有目錄和文件,之後對遍歷結果歸類:

修改OverrDirectories方法:

public static Dictionary<string,List<string>> OverDirectories(DirectoryInfo rootDirectory)
{
    var dict = new Dictionary<string, List<string>>();// 創建一個保存數據的 字典類型
    foreach(var file in rootDirectory.EnumerateFiles()) //枚舉當前目錄下的所有文件
    {
        var key = Path.GetFileNameWithoutExtension(file.Name); //獲取無擴展名的文件名
        if(!dict.ContainsKey(key)) //檢查dict是否存放過 文件名,如果沒有,則創建一個列表,如果有則在列表中添加一條文件的全路徑
        {
            dict[key] = new List<string>();
        }
        dict[key].Add(file.FullName);
    }

    // 枚舉當前目錄的子目錄,遞歸調用該方法
    var dirs = rootDirectory.EnumerateDirectories().Select(OverDirectories); 
    foreach(var dir in dirs)//處理返回的字典枚舉,將數據合併到當前dict變數中
    {
        foreach(var key in dir.Keys)
        {
            if(!dict.ContainsKey(key))
            {
                dict[key] = new List<string>();
            }
            dict[key].AddRange(dir[key]);
        }
    }
    // 返回結果
    return dict;
}

我們簡單測試一下,修改Main方法:

static void Main(string[] args)
{
    var drivers = GetDrivers();
    var results = OverDirectories(drivers[0]);
    Console.WriteLine(results);
}

嗯,如果不出意外的話,你應該能得到類似如下的提示:

image-20200506234510641

這是因為在系統中(不管哪種系統)會有一些文件或者目錄是我們沒有許可權訪問的,這時候就必須用try/catch處理這些沒有訪問許可權的目錄和文件。因為我們平時使用不會 把文件放到這些目錄下面,所以我們可以簡單的略過這些目錄。

同時觀察一下,GetDrivers 返回的是一組DirectoryInfo實例,而OverDirectories每次處理一個目錄,然後返回一個字典集合,所以我們需要整合這些集合,但我們在OverDirectories里編寫過相似的程式碼,為了減少重複程式碼的編寫,提取這部分程式碼為一個方法:

public static Dictionary<string,List<string>> Concat(params Dictionary<string,List<string>>[] dicts)
{
    var dict = new Dictionary<string,List<string>>();
    foreach(var dir in dicts)
    {
        foreach(var key in dir.Keys)
        {
            if(!dict.ContainsKey(key))
            {
                dict[key] = new List<string>();
            }
            dict[key].AddRange(dir[key]);
        }
    }
    return dict;
}

params 是C#可變參數列表關鍵字,聲明方式: params T[] values。表示方法可以接收任意個T類型的參數,方法中接到的是一個數組

繼續改造 OverDirectories方法,增加異常處理:

public static Dictionary<string,List<string>> OverDirectories(DirectoryInfo rootDirectory)
{
    var dict = new Dictionary<string, List<string>>();
    IEnumerable<FileInfo> files = new List<FileInfo>();
    try
    {
        files = rootDirectory.EnumerateFiles();
    }
    catch(Exception e)
    {
        Console.WriteLine($"錯誤資訊:{e}");//列印錯誤資訊
    }

    foreach(var file in files)
    {
        var key = Path.GetFileNameWithoutExtension(file.Name);
        if(!dict.ContainsKey(key))
        {
            dict[key] = new List<string>();
        }
        dict[key].Add(file.FullName);
    }

    try
    {
        var dicts = rootDirectory.EnumerateDirectories().Select(OverDirectories);    
        return Concat(dicts.Append(dict).ToArray());
    }
    catch (System.Exception e)
    {
        Console.WriteLine($"錯誤資訊:{e}");//列印錯誤資訊
    }
    return dict;
}

最後修改 Main方法,使其支援使用用戶輸入的字元串進行查詢:

static void Main(string[] args)
{
    var drivers = GetDrivers();
    var results = Concat(drivers.Select(OverDirectories).ToArray());
    Console.WriteLine("請輸入要查詢的文件名:");
    var search = Console.ReadLine().Trim();
    var keys = results.Keys.Where(p=>p.Contains(search));

    foreach(var key in keys)
    {
        var list = results[key];
        Console.WriteLine("查找到路徑是:");
        foreach(var path in list)
        {
            Console.WriteLine(path);
        }
    }
}

3. 總結

程式碼進行到這裡了,可以說基本功能已經完成。如果有小夥伴嘗試使用示例程式碼的話,可能會遇到各種問題,下一篇繼續為大家在現有知識基礎上做優化,讓它成為一個真正意義上可以使用的小工具。

更多內容煩請關注我的部落格《高先生小屋》

file

Tags: