小工具

  • 文章来源:

0. 前言

这是对C# 基础系列的一个总结,现在我们利用之前学到的知识做一个小小的工具来给我们使用。
如果有看过IO篇的小伙伴,应该有印象。当时我提过一个场景描述,我们在平时使用系统的时候,经常会为了找某个文件的位置而烦恼。那么我们现在尝试写一个控制台程序来帮助我们找文件的具体位置。

1. 分析

好,大家应该初步了解了需求内容。然后让我们来做一个简单的需求分析:

  1. 简单分析一下需求包括哪些功能点
  2. 规划各个功能点的实现方式

嗯,理论上讲还有一大堆的步骤,但因为是个练手的小项目就不扯那么多没用的了。简单来讲就是,分两步:

  1. 抓取系统可以访问的所有文件,并保存其全路径
  2. 根据输入的参数查询文件的全路径

需求分析完了,然后寻找可以实现的技术,我们现有的技术有IO、文件/路径操作、任务模式等技术,那么可以供我们选择的技术一目了然了:通过文件/目录/路径API访问所有的文件目录,使用字典保存,然后使用Linq查询文件所在目录。
OK,需求分析完了,技术也确认了。那么我们现在开始吧,小伙伴们跟紧了哦,车速不快的。

2. 开始

这里简单演示一下如何用Rider和VSCode、Visual Studio2019创建项目。

2.1. 创建一个名为 FileFinder的项目

a.使用Rider:
点击箭头所指方向:
C# 基础知识系列- 17 实战篇 编写一个小工具(1) - 图1
先在左侧选择Console Application,然后修改 Project name,最后修改 Solution Directory为自己的目录:
C# 基础知识系列- 17 实战篇 编写一个小工具(1) - 图2
然后点击 Create,创建完成结果如下:
C# 基础知识系列- 17 实战篇 编写一个小工具(1) - 图3
Rider创建项目的步骤在Windows、Linux、Mac三个系统都是一样的。
b. 使用VS Code创建项目
使用VS Code创建项目与Rider和Visual Studio有所不同,步骤比较繁琐:
先在合适的文件夹下创建一个fileFinder目录,并在fileFinder目录下打开命令行,输入以下命令:

  1. dotnet new sln -n fileFinder # 创建一个名为 fileFinder 的解决方案
  2. dotnet new console -n fileFinder # 创建一个名为 fileFinder的控制台程序
  3. dotnet sln add fileFinder # 把 fileFinder的项目添加到fileFinder的解决方案里

最终结果应该是这样的:
C# 基础知识系列- 17 实战篇 编写一个小工具(1) - 图4
c.使用 Visual Studio
C# 基础知识系列- 17 实战篇 编写一个小工具(1) - 图5
选择【创建新项目】
C# 基础知识系列- 17 实战篇 编写一个小工具(1) - 图6
注意框住地方的选择,选控制台程序,然后点击下一步
C# 基础知识系列- 17 实战篇 编写一个小工具(1) - 图7
填写项目名称、路径,点击创建
C# 基础知识系列- 17 实战篇 编写一个小工具(1) - 图8

2.2 开始编写程序

现在我们创建完成了一个项目,然后可以开始编写我们的程序了。
首先创建一个遍历所有目录的方法:

  1. public static Dictionary<string,List<string>> OverDirectories()
  2. {
  3. //
  4. return null;
  5. }

现在我们有一个问题,因为Windows的特殊性,目录结构分为了磁盘:\文件夹这种形式,我们没法通过设置一个根目录去遍历,这时候就要借助一下官方文档了。通过查阅API,我们发现一个类:

  1. public sealed class DriveInfo : System.Runtime.Serialization.ISerializable//提供对有关驱动器的信息的访问。

有一个方法:

  1. public static System.IO.DriveInfo[] GetDrives ();// 检索计算机上的所有逻辑驱动器的驱动器名称。

再看一下属性:

  1. public string Name { get; }// 获取驱动器的名称,如 C:\。
  2. public System.IO.DirectoryInfo RootDirectory { get; }// 获取驱动器的根目录。

初步查看满足我们的需要,先在Program.cs的头添加命名空间引用:

  1. using System.IO;

表示在这个代码文件中会使用这个命名空间的类或者结构体等元素。
在项目中编写一个方法:

  1. public static void GetDrivers()
  2. {
  3. var drives = DriveInfo.GetDrives();
  4. foreach(var drive in drives)
  5. {
  6. Console.WriteLine($"驱动器名称:{drive.Name}:\t {drive.RootDirectory}");
  7. }
  8. }

然后修改Main方法为:

  1. static void Main(string[] args)
  2. {
  3. GetDrivers();
  4. }

运行程序,下图是Linux系统的打印结果:(Rider/Visual Studio的运行程序快捷键是F5)
C# 基础知识系列- 17 实战篇 编写一个小工具(1) - 图9
经过完美符合我们的需求,修改GetDrivers方法,使其可以返回所有驱动器的根目录:
先引入以下命名空间的引用:

  1. using System.Linq;// Linq的支持
  2. using System.Collections.Generic;//泛型集合的支持

修改方法如下:

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

然后回到方法OverDirectories里,先获取所有的驱动器,遍历所有驱动器下的所有目录和文件,之后对遍历结果归类:
修改OverrDirectories方法:

  1. public static Dictionary<string,List<string>> OverDirectories(DirectoryInfo rootDirectory)
  2. {
  3. var dict = new Dictionary<string, List<string>>();// 创建一个保存数据的 字典类型
  4. foreach(var file in rootDirectory.EnumerateFiles()) //枚举当前目录下的所有文件
  5. {
  6. var key = Path.GetFileNameWithoutExtension(file.Name); //获取无扩展名的文件名
  7. if(!dict.ContainsKey(key)) //检查dict是否存放过 文件名,如果没有,则创建一个列表,如果有则在列表中添加一条文件的全路径
  8. {
  9. dict[key] = new List<string>();
  10. }
  11. dict[key].Add(file.FullName);
  12. }
  13. // 枚举当前目录的子目录,递归调用该方法
  14. var dirs = rootDirectory.EnumerateDirectories().Select(OverDirectories);
  15. foreach(var dir in dirs)//处理返回的字典枚举,将数据合并到当前dict变量中
  16. {
  17. foreach(var key in dir.Keys)
  18. {
  19. if(!dict.ContainsKey(key))
  20. {
  21. dict[key] = new List<string>();
  22. }
  23. dict[key].AddRange(dir[key]);
  24. }
  25. }
  26. // 返回结果
  27. return dict;
  28. }

我们简单测试一下,修改Main方法:

  1. static void Main(string[] args)
  2. {
  3. var drivers = GetDrivers();
  4. var results = OverDirectories(drivers[0]);
  5. Console.WriteLine(results);
  6. }

嗯,如果不出意外的话,你应该能得到类似如下的提示:
C# 基础知识系列- 17 实战篇 编写一个小工具(1) - 图10
这是因为在系统中(不管哪种系统)会有一些文件或者目录是我们没有权限访问的,这时候就必须用try/catch处理这些没有访问权限的目录和文件。因为我们平时使用不会 把文件放到这些目录下面,所以我们可以简单的略过这些目录。
同时观察一下,GetDrivers 返回的是一组DirectoryInfo实例,而OverDirectories每次处理一个目录,然后返回一个字典集合,所以我们需要整合这些集合,但我们在OverDirectories里编写过相似的代码,为了减少重复代码的编写,提取这部分代码为一个方法:

  1. public static Dictionary<string,List<string>> Concat(params Dictionary<string,List<string>>[] dicts)
  2. {
  3. var dict = new Dictionary<string,List<string>>();
  4. foreach(var dir in dicts)
  5. {
  6. foreach(var key in dir.Keys)
  7. {
  8. if(!dict.ContainsKey(key))
  9. {
  10. dict[key] = new List<string>();
  11. }
  12. dict[key].AddRange(dir[key]);
  13. }
  14. }
  15. return dict;
  16. }

params 是C#可变参数列表关键字,声明方式: params T[] values。表示方法可以接收任意个T类型的参数,方法中接到的是一个数组

继续改造 OverDirectories方法,增加异常处理:

  1. public static Dictionary<string,List<string>> OverDirectories(DirectoryInfo rootDirectory)
  2. {
  3. var dict = new Dictionary<string, List<string>>();
  4. IEnumerable<FileInfo> files = new List<FileInfo>();
  5. try
  6. {
  7. files = rootDirectory.EnumerateFiles();
  8. }
  9. catch(Exception e)
  10. {
  11. Console.WriteLine($"错误信息:{e}");//打印错误信息
  12. }
  13. foreach(var file in files)
  14. {
  15. var key = Path.GetFileNameWithoutExtension(file.Name);
  16. if(!dict.ContainsKey(key))
  17. {
  18. dict[key] = new List<string>();
  19. }
  20. dict[key].Add(file.FullName);
  21. }
  22. try
  23. {
  24. var dicts = rootDirectory.EnumerateDirectories().Select(OverDirectories);
  25. return Concat(dicts.Append(dict).ToArray());
  26. }
  27. catch (System.Exception e)
  28. {
  29. Console.WriteLine($"错误信息:{e}");//打印错误信息
  30. }
  31. return dict;
  32. }

最后修改 Main方法,使其支持使用用户输入的字符串进行查询:

  1. static void Main(string[] args)
  2. {
  3. var drivers = GetDrivers();
  4. var results = Concat(drivers.Select(OverDirectories).ToArray());
  5. Console.WriteLine("请输入要查询的文件名:");
  6. var search = Console.ReadLine().Trim();
  7. var keys = results.Keys.Where(p=>p.Contains(search));
  8. foreach(var key in keys)
  9. {
  10. var list = results[key];
  11. Console.WriteLine("查找到路径是:");
  12. foreach(var path in list)
  13. {
  14. Console.WriteLine(path);
  15. }
  16. }
  17. }

3. 总结

代码进行到这里了,可以说基本功能已经完成。如果有小伙伴尝试使用示例代码的话,可能会遇到各种问题,下一篇继续为大家在现有知识基础上做优化,让它成为一个真正意义上可以使用的小工具。


  • 本文作者:GeekPower - Felix Sun
  • 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!