C# 基础知识系列- 17 实战篇 编写一个小工具(1)
0. 前言
这是对c# 基础系列的一个总结,现在我们利用之前学到的知识做一个小小的工具来给我们使用。
如果有看过io篇的小伙伴,应该有印象。当时我提过一个场景描述,我们在平时使用系统的时候,经常会为了找某个文件的位置而烦恼。那么我们现在尝试写一个控制台程序来帮助我们找文件的具体位置。
1. 分析
好,大家应该初步了解了需求内容。然后让我们来做一个简单的需求分析:
- 简单分析一下需求包括哪些功能点
- 规划各个功能点的实现方式
嗯,理论上讲还有一大堆的步骤,但因为是个练手的小项目就不扯那么多没用的了。简单来讲就是,分两步:
- 抓取系统可以访问的所有文件,并保存其全路径
- 根据输入的参数查询文件的全路径
需求分析完了,然后寻找可以实现的技术,我们现有的技术有io、文件/路径操作、任务模式等技术,那么可以供我们选择的技术一目了然了:通过文件/目录/路径api访问所有的文件目录,使用字典保存,然后使用linq查询文件所在目录。
ok,需求分析完了,技术也确认了。那么我们现在开始吧,小伙伴们跟紧了哦,车速不快的。
2. 开始
这里简单演示一下如何用rider和vscode、visual studio2019创建项目。
2.1. 创建一个名为 filefinder的项目
a.使用rider:
点击箭头所指方向:
先在左侧选择console application,然后修改 project name,最后修改 solution directory为自己的目录:
然后点击 create,创建完成结果如下:
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的解决方案里
最终结果应该是这样的:
c.使用 visual studio
选择【创建新项目】
注意框住地方的选择,选控制台程序,然后点击下一步
填写项目名称、路径,点击创建
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)
经过完美符合我们的需求,修改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); }
嗯,如果不出意外的话,你应该能得到类似如下的提示:
这是因为在系统中(不管哪种系统)会有一些文件或者目录是我们没有权限访问的,这时候就必须用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. 总结
代码进行到这里了,可以说基本功能已经完成。如果有小伙伴尝试使用示例代码的话,可能会遇到各种问题,下一篇继续为大家在现有知识基础上做优化,让它成为一个真正意义上可以使用的小工具。
更多内容烦请关注
上一篇: linux中的相对路径的表示方法