本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:在C#开发中,获取本地磁盘目录并处理文件是常见的基础操作,涉及 System.IO 命名空间中的 DirectoryInfo 、文件遍历、递归处理、权限控制等内容。本实例源码详细演示了如何使用C#实现磁盘目录的获取与遍历,包含同步与异步方法、LINQ查询、异常处理、路径操作等关键技术点。通过完整示例代码,开发者可以快速掌握文件系统的操作技巧,并应用于实际项目中。

1. C#文件系统操作概述

在现代软件开发中,文件系统操作是构建稳定、高效应用程序的基础环节之一。C#作为微软生态中核心的编程语言之一,提供了强大的System.IO命名空间来支持开发者对文件和目录的管理。本章将引导读者了解C#在Windows平台下进行文件系统操作的基本机制。

通过System.IO中的关键类如 Directory File Path DirectoryInfo FileInfo ,我们将掌握如何创建、读取、更新和删除文件与目录资源。同时,本章也将简要介绍文件与目录之间的逻辑关系,并讨论如何在代码中安全地访问本地磁盘,避免权限异常和资源冲突。这将为后续章节中更复杂的数据遍历、筛选与处理打下坚实的基础。

2. DirectoryInfo类的使用

在C#中, DirectoryInfo 类是 System.IO 命名空间下的核心类之一,用于封装对文件系统中目录的操作。与静态类 Directory 相比, DirectoryInfo 提供了面向对象的编程方式,更适合处理需要多次操作目录对象的场景。本章将详细介绍 DirectoryInfo 类的使用方法,包括其基本功能、与其他IO类的对比、常见操作方法以及在多线程环境下的性能优化策略。

2.1 DirectoryInfo类的基本功能

DirectoryInfo 类不仅提供了获取目录基本信息的能力,还支持创建、删除、移动等常用操作。通过实例化一个 DirectoryInfo 对象,可以更高效地进行目录操作,尤其是在需要多次访问同一目录时。

2.1.1 DirectoryInfo类的定义与实例化

DirectoryInfo 类位于 System.IO 命名空间中,其构造函数接收一个表示目录路径的字符串。通过构造函数可以创建一个表示特定目录的对象。

using System;
using System.IO;

class Program
{
    static void Main()
    {
        string path = @"C:\ExampleDirectory";
        DirectoryInfo directoryInfo = new DirectoryInfo(path);
        Console.WriteLine($"Directory Path: {directoryInfo.FullName}");
    }
}

代码逻辑分析:

  • 第1行引入 System 命名空间,用于控制台输出。
  • 第2行引入 System.IO 命名空间,以便使用 DirectoryInfo 类。
  • 第6行定义变量 path ,表示目标目录路径。
  • 第7行使用 new DirectoryInfo(path) 实例化一个 DirectoryInfo 对象,指向指定路径。
  • 第9行输出该目录的完整路径( FullName 属性)。

参数说明:

  • path :表示目录的路径字符串,可以是绝对路径或相对路径。
  • directoryInfo :实例化后的对象,封装了对目标目录的所有操作能力。

2.1.2 获取目录的基本信息(如创建时间、名称、父目录等)

DirectoryInfo 类提供了多个属性用于获取目录的基本信息,包括创建时间、父目录、是否为只读等。

using System;
using System.IO;

class Program
{
    static void Main()
    {
        string path = @"C:\ExampleDirectory";
        DirectoryInfo directoryInfo = new DirectoryInfo(path);
        Console.WriteLine($"Name: {directoryInfo.Name}");
        Console.WriteLine($"Parent: {directoryInfo.Parent?.Name ?? "No parent"}");
        Console.WriteLine($"Creation Time: {directoryInfo.CreationTime}");
        Console.WriteLine($"Attributes: {directoryInfo.Attributes}");
    }
}

代码逻辑分析:

  • 第8行输出目录名称( Name 属性)。
  • 第9行输出父目录名称,使用 ?. 运算符避免空引用异常。
  • 第10行输出目录创建时间。
  • 第11行输出目录的属性(如只读、隐藏等)。

参数说明:

属性名 类型 说明
Name string 当前目录的名称
Parent DirectoryInfo 父目录对象,可能为null
CreationTime DateTime 目录创建时间
Attributes FileAttributes 目录的属性标志,如只读、隐藏

mermaid流程图:

graph TD
    A[创建DirectoryInfo实例] --> B{路径是否存在}
    B -->|是| C[获取目录信息]
    B -->|否| D[抛出异常]
    C --> E[输出Name]
    C --> F[输出Parent]
    C --> G[输出CreationTime]
    C --> H[输出Attributes]

2.2 DirectoryInfo与其他IO类的比较

在C#中,除了 DirectoryInfo ,还有 Directory FileInfo File 等类用于文件和目录操作。它们各有优劣,适用于不同场景。

2.2.1 DirectoryInfo与Directory类的差异

特性 DirectoryInfo Directory(静态类)
实例化方式 需要实例化 直接调用静态方法
性能 多次操作时更高效 每次调用都重新访问文件系统
面向对象特性 支持面向对象操作 不支持面向对象
属性访问 可缓存属性 每次调用都重新读取属性
适用场景 需要多次操作同一目录时 单次操作目录

示例对比:

// 使用 DirectoryInfo
DirectoryInfo dir = new DirectoryInfo(@"C:\ExampleDirectory");
Console.WriteLine(dir.Name);
Console.WriteLine(dir.Exists);

// 使用 Directory
Console.WriteLine(Directory.Exists(@"C:\ExampleDirectory"));
Console.WriteLine(Directory.GetParent(@"C:\ExampleDirectory")?.Name);

结论:

  • DirectoryInfo 更适合需要多次访问目录对象的场景。
  • Directory 类适合一次性操作目录,使用更简洁。

2.2.2 FileInfo与File类的使用场景对比

特性 FileInfo File(静态类)
实例化方式 需要实例化 直接调用静态方法
性能 多次操作时更高效 每次调用都重新访问文件系统
属性访问 可缓存属性 每次调用都重新读取属性
适用场景 需要多次操作同一文件时 单次操作文件

示例对比:

// 使用 FileInfo
FileInfo file = new FileInfo(@"C:\ExampleDirectory\test.txt");
Console.WriteLine(file.Length);
Console.WriteLine(file.Exists);

// 使用 File
Console.WriteLine(File.Exists(@"C:\ExampleDirectory\test.txt"));
Console.WriteLine(new FileInfo(@"C:\ExampleDirectory\test.txt").Length);

2.3 DirectoryInfo的常见操作方法

DirectoryInfo 类提供了多种操作目录的方法,包括创建、删除、移动等,适用于各种文件系统操作需求。

2.3.1 创建与删除目录

using System;
using System.IO;

class Program
{
    static void Main()
    {
        string path = @"C:\NewDirectory";
        DirectoryInfo directoryInfo = new DirectoryInfo(path);

        if (!directoryInfo.Exists)
        {
            directoryInfo.Create();
            Console.WriteLine("Directory created.");
        }

        // 删除目录
        if (directoryInfo.Exists)
        {
            directoryInfo.Delete(true); // true表示递归删除子目录和文件
            Console.WriteLine("Directory deleted.");
        }
    }
}

代码逻辑分析:

  • 第8行检查目录是否存在。
  • 第10行调用 Create() 方法创建目录。
  • 第15行调用 Delete(true) 递归删除目录及其内容。

2.3.2 移动目录与重命名

虽然 DirectoryInfo 类本身不直接支持重命名操作,但可以通过 MoveTo 方法实现。

using System;
using System.IO;

class Program
{
    static void Main()
    {
        string sourcePath = @"C:\OldDirectoryName";
        string destPath = @"C:\NewDirectoryName";

        DirectoryInfo directoryInfo = new DirectoryInfo(sourcePath);
        if (directoryInfo.Exists)
        {
            directoryInfo.MoveTo(destPath);
            Console.WriteLine("Directory renamed/moved.");
        }
    }
}

参数说明:

  • sourcePath :原始目录路径。
  • destPath :目标路径,可以是新的目录名或新位置。

2.3.3 获取目录的属性信息

DirectoryInfo 类提供了一系列属性用于获取目录的详细信息:

属性名 类型 说明
Exists bool 目录是否存在
FullName string 目录的完整路径
Extension string 目录扩展名(通常为空)
CreationTime DateTime 创建时间
LastAccessTime DateTime 最后一次访问时间
LastWriteTime DateTime 最后一次修改时间
Attributes FileAttributes 目录属性(如隐藏、只读等)

2.4 DirectoryInfo的线程安全性与性能优化

在多线程环境中操作文件系统时,需要注意线程安全性和性能优化问题。

2.4.1 多线程环境下的目录访问

DirectoryInfo 类本身不是线程安全的,多个线程同时访问同一 DirectoryInfo 实例可能会导致数据不一致或异常。

解决方案:

  • 使用锁机制( lock )确保同一时间只有一个线程访问。
  • 避免共享 DirectoryInfo 实例,每个线程独立操作。
private static readonly object lockObj = new object();

public static void SafeDirectoryAccess(string path)
{
    lock (lockObj)
    {
        DirectoryInfo dir = new DirectoryInfo(path);
        if (dir.Exists)
        {
            Console.WriteLine(dir.Name);
        }
    }
}

2.4.2 使用缓存提升访问效率

频繁访问同一目录时,可以将 DirectoryInfo 对象缓存起来,避免重复实例化和系统调用。

private static Dictionary<string, DirectoryInfo> directoryCache = new Dictionary<string, DirectoryInfo>();

public static DirectoryInfo GetCachedDirectory(string path)
{
    if (!directoryCache.TryGetValue(path, out DirectoryInfo dir))
    {
        dir = new DirectoryInfo(path);
        directoryCache[path] = dir;
    }
    return dir;
}

优点:

  • 减少系统调用次数,提高性能。
  • 避免重复创建对象,节省内存资源。

注意事项:

  • 缓存对象需要考虑生命周期管理,避免内存泄漏。
  • 文件系统状态变化后,缓存数据可能失效,需及时更新或清除。

3. GetDirectories方法获取子目录与GetFiles方法读取目录文件

3.1 获取子目录与文件的核心方法

3.1.1 GetDirectories方法的使用方式

GetDirectories 方法是 DirectoryInfo 类的重要成员之一,用于获取指定目录下的所有子目录。该方法有多个重载版本,最基础的使用方式如下:

DirectoryInfo rootDir = new DirectoryInfo(@"C:\Example");
DirectoryInfo[] subDirectories = rootDir.GetDirectories();

逐行代码解析:

  • 第1行:实例化一个 DirectoryInfo 对象,传入目标目录路径。
  • 第2行:调用 GetDirectories() 方法,返回当前目录下的所有子目录数组。

方法签名:

public DirectoryInfo[] GetDirectories();

该方法返回的是 DirectoryInfo[] 类型数组,其中每个元素都代表一个子目录。如果目录中没有子目录,则返回一个长度为0的数组。

3.1.2 GetFiles方法的参数详解

GetFiles 方法用于获取指定目录下的所有文件。与 GetDirectories 类似,它也提供了多个重载方法,以支持不同的搜索条件。

基础用法:

DirectoryInfo dir = new DirectoryInfo(@"C:\Example");
FileInfo[] files = dir.GetFiles();

此代码获取目录下所有文件,不进行任何筛选。

带搜索模式的用法:

FileInfo[] csFiles = dir.GetFiles("*.cs");

这里传入了 "*.cs" 作为搜索模式,表示只获取扩展名为 .cs 的文件。

完整参数用法:

FileInfo[] files = dir.GetFiles("*.txt", SearchOption.AllDirectories);
  • 第一个参数是搜索模式,例如 "*.txt"
  • 第二个参数是 SearchOption 枚举,用于控制搜索范围:
  • SearchOption.TopDirectoryOnly :仅当前目录。
  • SearchOption.AllDirectories :递归搜索所有子目录。

表格:GetDirectories 与 GetFiles 方法对比

方法名 返回类型 是否支持搜索模式 是否支持递归搜索 常见用途
GetDirectories() DirectoryInfo[] 获取子目录
GetFiles() FileInfo[] 获取文件,支持筛选与递归搜索

3.2 筛选目录与文件的高级技巧

3.2.1 使用搜索模式匹配文件名

GetFiles 支持使用通配符进行文件名匹配。常见的通配符包括:

  • * :匹配任意数量的字符
  • ? :匹配单个字符

示例:

// 获取所有 .cs 和 .vb 文件
FileInfo[] codeFiles = dir.GetFiles("*.cs");
FileInfo[] vbFiles = dir.GetFiles("*.vb");

进阶技巧:结合通配符灵活筛选

// 匹配以 "log" 开头的 txt 文件
FileInfo[] logFiles = dir.GetFiles("log*.txt");

这样可以获取类似 log1.txt logfile.txt 等文件。

3.2.2 搜索选项(TopDirectoryOnly 与 AllDirectories)

SearchOption 枚举是 GetDirectories GetFiles 的可选参数,用于控制搜索的范围。

示例:

// 仅当前目录下的所有 .txt 文件
FileInfo[] topFiles = dir.GetFiles("*.txt", SearchOption.TopDirectoryOnly);

// 当前目录及其所有子目录中的 .txt 文件
FileInfo[] allFiles = dir.GetFiles("*.txt", SearchOption.AllDirectories);

流程图:展示搜索范围差异

graph TD
    A[目录结构] --> B{搜索选项}
    B -->|TopDirectoryOnly| C[仅当前目录]
    B -->|AllDirectories| D[递归遍历所有子目录]

性能提示:

  • 使用 AllDirectories 会显著增加 I/O 操作次数,尤其在处理大型目录结构时,应谨慎使用。
  • 若仅需当前目录内容,建议始终使用 TopDirectoryOnly 以提升性能。

3.3 获取结果的处理与优化

3.3.1 遍历大型目录结构的性能考量

当处理包含大量子目录和文件的路径时, GetDirectories GetFiles 的性能会受到显著影响。主要瓶颈包括:

  • 磁盘访问延迟 :频繁读取文件系统元数据。
  • 内存占用 :返回的数组可能包含大量对象,占用较多内存。
  • UI冻结 :在图形界面中直接调用可能导致界面卡顿。

优化策略:

  1. 分页加载 :按需加载目录内容,而非一次性获取全部。
  2. 缓存目录信息 :将已获取的目录/文件信息缓存,避免重复读取。
  3. 异步加载 :通过 Task BackgroundWorker 在后台加载数据。

3.3.2 并行处理目录与文件的策略

为了提升性能,可以使用多线程或并行 LINQ(PLINQ)来处理目录内容。

示例:并行处理文件列表

var files = dir.GetFiles("*.log", SearchOption.AllDirectories);
Parallel.ForEach(files, file =>
{
    Console.WriteLine($"处理文件: {file.FullName}");
    // 模拟耗时操作
    Thread.Sleep(10);
});

逐行解释:

  • Parallel.ForEach :使用并行方式遍历每个文件。
  • file.FullName :获取文件的完整路径。
  • Thread.Sleep(10) :模拟处理文件的耗时操作。

注意事项:

  • 并行处理应控制线程数量,避免资源争用。
  • 若处理逻辑涉及共享资源(如日志文件),需使用锁机制确保线程安全。

对比表格:串行与并行处理性能对比

处理方式 时间复杂度 是否适合大数据 是否易导致资源竞争 适用场景
串行 O(n) 小型目录或单线程环境
并行 O(n/p) 大型目录、后台处理任务

3.4 获取目录信息的异常处理机制

3.4.1 访问受限目录时的异常捕获

在实际应用中,某些目录可能因权限问题无法访问,此时调用 GetDirectories() GetFiles() 会抛出 UnauthorizedAccessException 异常。

示例代码:捕获权限异常

try
{
    DirectoryInfo dir = new DirectoryInfo(@"C:\Windows\System32");
    DirectoryInfo[] subDirs = dir.GetDirectories();
}
catch (UnauthorizedAccessException ex)
{
    Console.WriteLine($"无法访问目录:{ex.Message}");
}

异常处理建议:

  • 使用 try-catch 块包裹文件系统访问代码。
  • 提供用户提示或日志记录,便于排查问题。
  • 对于系统关键目录(如 C:\Windows ),应避免直接访问。

3.4.2 文件正在使用或被锁定的处理

当某个文件正在被其他进程使用时,尝试访问它可能会引发 IOException

示例:处理被锁定的文件

try
{
    FileInfo[] files = dir.GetFiles("*.log");
    foreach (var file in files)
    {
        using (FileStream fs = file.OpenRead())
        {
            // 读取文件内容
        }
    }
}
catch (IOException ex)
{
    Console.WriteLine($"文件正在使用中:{ex.Message}");
}

处理策略:

  • 在打开文件前检查是否被锁定。
  • 使用 FileShare 参数控制文件访问权限。
  • 若文件无法立即访问,可延迟重试或跳过处理。

异常类型与对应处理建议表:

异常类型 原因说明 建议处理方式
UnauthorizedAccessException 没有访问权限 检查运行权限或跳过该目录
IOException 文件正在使用或磁盘错误 重试或跳过当前文件
DirectoryNotFoundException 指定路径不存在 检查路径有效性或跳过
SecurityException 安全策略限制访问 调整应用程序权限或联系管理员

本章总结:

通过对 DirectoryInfo GetDirectories GetFiles 方法的深入解析,我们掌握了如何高效获取目录结构与文件列表的方法,并结合搜索模式、递归选项和异常处理机制,提升了代码的健壮性与性能。下一章将探讨如何通过递归方式遍历目录结构,并结合 LINQ 实现更灵活的文件集合处理。

4. 递归遍历目录结构与使用LINQ处理文件集合

4.1 递归遍历的实现原理

4.1.1 基于递归函数的目录遍历

递归是一种在编程中常用的解决问题的方法,特别适用于需要遍历树状结构的场景,例如文件系统中的目录结构。递归函数通过调用自身来处理子问题,直到达到终止条件。

在C#中,使用 DirectoryInfo 类可以实现目录的递归遍历。以下是一个基本的递归函数示例:

public void RecursiveDirectorySearch(DirectoryInfo root)
{
    try
    {
        // 获取当前目录下的所有子目录
        foreach (DirectoryInfo dir in root.GetDirectories())
        {
            Console.WriteLine($"Directory: {dir.FullName}");
            RecursiveDirectorySearch(dir); // 递归调用
        }

        // 获取当前目录下的所有文件
        foreach (FileInfo file in root.GetFiles())
        {
            Console.WriteLine($"File: {file.FullName}");
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Error accessing {root.FullName}: {ex.Message}");
    }
}
代码逻辑分析:
  1. 入口参数 DirectoryInfo root 表示要开始遍历的根目录。
  2. 递归结构
    - foreach (DirectoryInfo dir in root.GetDirectories()) :获取当前目录下的所有子目录。
    - RecursiveDirectorySearch(dir) :对每个子目录进行递归调用,实现深度优先遍历。
  3. 文件处理
    - foreach (FileInfo file in root.GetFiles()) :获取当前目录下的所有文件并输出路径。
  4. 异常处理 :使用 try-catch 块捕获访问受限目录时可能出现的异常。
参数说明:
  • DirectoryInfo :用于表示文件系统中的目录,提供获取子目录、文件和属性的方法。
  • GetDirectories() :返回当前目录下的所有子目录。
  • GetFiles() :返回当前目录下的所有文件。
递归流程图:
graph TD
    A[开始遍历] --> B{是否有子目录?}
    B -->|是| C[遍历子目录]
    C --> D[递归调用自身]
    D --> E[继续遍历下一级目录]
    B -->|否| F[遍历当前目录下的文件]
    F --> G[输出文件信息]
    E --> H[返回上一级]

4.1.2 使用队列与栈实现非递归遍历

虽然递归方法简洁易懂,但在处理非常大的目录结构时可能会导致栈溢出。为了避免这个问题,可以使用队列(广度优先)或栈(深度优先)实现非递归的目录遍历。

使用队列实现广度优先遍历:
public void BreadthFirstDirectorySearch(string rootPath)
{
    Queue<DirectoryInfo> directoryQueue = new Queue<DirectoryInfo>();
    DirectoryInfo root = new DirectoryInfo(rootPath);

    directoryQueue.Enqueue(root);

    while (directoryQueue.Count > 0)
    {
        DirectoryInfo currentDir = directoryQueue.Dequeue();
        Console.WriteLine($"Directory: {currentDir.FullName}");

        try
        {
            foreach (DirectoryInfo dir in currentDir.GetDirectories())
            {
                directoryQueue.Enqueue(dir); // 将子目录加入队列
            }

            foreach (FileInfo file in currentDir.GetFiles())
            {
                Console.WriteLine($"File: {file.FullName}");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error accessing {currentDir.FullName}: {ex.Message}");
        }
    }
}
使用栈实现深度优先遍历:
public void DepthFirstDirectorySearch(string rootPath)
{
    Stack<DirectoryInfo> directoryStack = new Stack<DirectoryInfo>();
    DirectoryInfo root = new DirectoryInfo(rootPath);

    directoryStack.Push(root);

    while (directoryStack.Count > 0)
    {
        DirectoryInfo currentDir = directoryStack.Pop();
        Console.WriteLine($"Directory: {currentDir.FullName}");

        try
        {
            // 深度优先,先处理子目录
            foreach (DirectoryInfo dir in currentDir.GetDirectories())
            {
                directoryStack.Push(dir); // 将子目录压入栈
            }

            foreach (FileInfo file in currentDir.GetFiles())
            {
                Console.WriteLine($"File: {file.FullName}");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error accessing {currentDir.FullName}: {ex.Message}");
        }
    }
}
对比分析:
方法类型 遍历顺序 数据结构 适用场景
递归 深度优先 调用栈 简单目录结构
队列(广度优先) 广度优先 队列 大型目录结构,需按层处理
栈(深度优先) 深度优先 大型目录结构,需深度优先

4.2 递归遍历的优化与控制

4.2.1 限制递归深度与遍历范围

在实际应用中,可能并不需要遍历整个目录树,而是仅关注特定深度的子目录。可以通过传递当前深度参数,并设置最大深度来控制递归的终止。

public void LimitedRecursiveSearch(DirectoryInfo root, int currentDepth, int maxDepth)
{
    if (currentDepth > maxDepth)
        return;

    Console.WriteLine($"{new string(' ', currentDepth * 2)}Directory: {root.FullName}");

    try
    {
        foreach (DirectoryInfo dir in root.GetDirectories())
        {
            LimitedRecursiveSearch(dir, currentDepth + 1, maxDepth);
        }

        foreach (FileInfo file in root.GetFiles())
        {
            Console.WriteLine($"{new string(' ', (currentDepth + 1) * 2)}File: {file.FullName}");
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"{new string(' ', currentDepth * 2)}Error accessing {root.FullName}: {ex.Message}");
    }
}
参数说明:
  • currentDepth :当前递归深度。
  • maxDepth :最大允许递归深度。

4.2.2 跳过特定目录或文件的策略

有时需要忽略某些特定的目录或文件(如临时文件夹 .git bin 等),可以通过过滤器实现:

private static readonly HashSet<string> SkipDirectories = new HashSet<string> { ".git", "bin", "obj" };

public void SkipDirectorySearch(DirectoryInfo root)
{
    Console.WriteLine($"Directory: {root.FullName}");

    try
    {
        foreach (DirectoryInfo dir in root.GetDirectories())
        {
            if (SkipDirectories.Contains(dir.Name)) continue;

            SkipDirectorySearch(dir);
        }

        foreach (FileInfo file in root.GetFiles())
        {
            Console.WriteLine($"File: {file.FullName}");
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Error accessing {root.FullName}: {ex.Message}");
    }
}
过滤策略说明:
  • 使用 HashSet<string> 存储需要跳过的目录名,提升查找效率。
  • 在递归调用前检查目录名是否在黑名单中,若在则跳过该目录。

4.3 使用LINQ进行文件集合筛选

4.3.1 LINQ查询的基本语法

LINQ(Language Integrated Query)是C#中用于查询集合的强大工具。通过LINQ,我们可以对文件集合进行过滤、排序和投影等操作。

DirectoryInfo root = new DirectoryInfo(@"C:\ExampleDir");

var query = from file in root.GetFiles("*.*", SearchOption.AllDirectories)
            where file.Length > 10000 && file.Extension == ".txt"
            orderby file.CreationTime descending
            select new
            {
                FileName = file.Name,
                Size = file.Length,
                Created = file.CreationTime
            };
代码逻辑分析:
  1. 获取文件集合
    - GetFiles("*.*", SearchOption.AllDirectories) :递归获取所有文件。
  2. 过滤条件
    - where file.Length > 10000 :文件大小大于10KB。
    - file.Extension == ".txt" :扩展名为 .txt
  3. 排序
    - orderby file.CreationTime descending :按创建时间降序排列。
  4. 投影
    - select new { ... } :选择特定属性组成匿名类型。

4.3.2 过滤、排序与投影文件集合

除了基本的LINQ查询,还可以结合 GroupBy Aggregate 等高级操作对文件进行更复杂的处理。

按扩展名分组统计文件数量:
var grouped = root.GetFiles("*.*", SearchOption.AllDirectories)
                  .GroupBy(f => f.Extension)
                  .Select(g => new
                  {
                      Extension = g.Key,
                      Count = g.Count(),
                      TotalSize = g.Sum(f => f.Length)
                  });

foreach (var group in grouped)
{
    Console.WriteLine($"{group.Extension}: {group.Count} files, Total size: {group.TotalSize} bytes");
}
输出示例:
Extension Count Total size (bytes)
.txt 23 125000
.jpg 15 870000
.log 7 95000

4.4 LINQ与异步处理的结合应用

4.4.1 异步加载文件列表

在处理大型目录时,同步操作可能导致UI冻结。通过异步编程模型可以提高用户体验:

public async Task<List<FileInfo>> GetFilesAsync(string path)
{
    return await Task.Run(() =>
    {
        DirectoryInfo root = new DirectoryInfo(path);
        return root.GetFiles("*.*", SearchOption.AllDirectories).ToList();
    });
}
调用示例:
var files = await GetFilesAsync(@"C:\LargeDir");
var txtFiles = files.Where(f => f.Extension == ".txt").ToList();

4.4.2 使用LINQ聚合文件信息

结合LINQ与异步处理,可以高效地聚合文件信息,例如计算总大小、平均大小等:

var result = await GetFilesAsync(@"C:\Data");

double totalSize = result.Sum(f => f.Length);
double averageSize = result.Average(f => f.Length);
int fileCount = result.Count;

Console.WriteLine($"Total size: {totalSize} bytes");
Console.WriteLine($"Average size: {averageSize:F2} bytes");
Console.WriteLine($"Total files: {fileCount}");
性能对比表格:
方法类型 适用场景 性能表现 优点
同步LINQ 小型目录 简洁直观
异步LINQ UI交互或大型目录 稍慢 避免UI冻结,响应性强
并行LINQ 多核CPU处理大型目录 极快 利用多线程提升性能

通过本章的学习,我们掌握了递归与非递归遍历目录的实现方式、目录遍历的优化策略、以及如何结合LINQ进行高效的文件筛选与处理。这些技术为后续的文件系统开发与性能优化打下了坚实的基础。

5. 路径处理、权限控制与完整示例演示

5.1 Path类的路径拼接与解析

System.IO.Path 类提供了一系列静态方法,用于操作路径字符串,而无需实际访问文件系统。这在处理路径拼接、解析、验证等场景中非常实用。

5.1.1 构建跨平台兼容的路径字符串

在C#中,推荐使用 Path.Combine() 方法来拼接路径字符串,这样可以自动根据操作系统选择正确的路径分隔符(如Windows使用 \ ,而Linux/macOS使用 / )。

string path1 = Path.Combine("C:\\Projects", "Data", "Files");
Console.WriteLine(path1);  // 输出:C:\Projects\Data\Files

说明
- Path.Combine(params string[] paths) 方法可以接受多个路径字符串进行拼接。
- 避免手动拼接路径字符串,防止因不同平台导致的路径格式问题。

5.1.2 提取文件名、扩展名与根目录

以下是一些常用的路径解析方法:

方法名 用途 示例
Path.GetFileName() 获取文件名及扩展名 Path.GetFileName("C:\\test\\file.txt") file.txt
Path.GetFileNameWithoutExtension() 获取不带扩展的文件名 Path.GetFileNameWithoutExtension("file.txt") file
Path.GetExtension() 获取扩展名 Path.GetExtension("file.txt") .txt
Path.GetDirectoryName() 获取路径中的目录部分 Path.GetDirectoryName("C:\\test\\file.txt") C:\test
Path.GetPathRoot() 获取根目录 Path.GetPathRoot("C:\\test\\file.txt") C:\

示例代码:

string fullPath = @"C:\Projects\Source\Readme.md";

Console.WriteLine("文件名:" + Path.GetFileName(fullPath));  // Readme.md
Console.WriteLine("扩展名:" + Path.GetExtension(fullPath));  // .md
Console.WriteLine("目录名:" + Path.GetDirectoryName(fullPath));  // C:\Projects\Source
Console.WriteLine("根目录:" + Path.GetPathRoot(fullPath));  // C:\

5.2 DirectorySecurity类控制目录权限

DirectorySecurity 类允许我们在C#中获取或设置目录的访问控制列表(ACL),从而实现对目录权限的精细控制。

5.2.1 获取与修改目录的访问控制列表(ACL)

我们可以使用 Directory.GetAccessControl() 方法获取目录的安全信息,并通过 Directory.SetAccessControl() 方法进行更新。

string dirPath = @"C:\SecureFolder";

DirectorySecurity dirSecurity = Directory.GetAccessControl(dirPath);

// 添加一条访问规则:允许当前用户完全控制该目录
dirSecurity.AddAccessRule(new FileSystemAccessRule(
    Environment.UserName,
    FileSystemRights.FullControl,
    AccessControlType.Allow));

Directory.SetAccessControl(dirPath, dirSecurity);

参数说明
- Environment.UserName :当前用户的名称。
- FileSystemRights.FullControl :授予完全控制权限。
- AccessControlType.Allow :允许该权限。

5.2.2 添加、删除与修改用户权限

以下是一个更完整的示例,演示如何添加、删除、修改权限规则:

string dirPath = @"C:\SecureFolder";
DirectorySecurity dirSecurity = Directory.GetAccessControl(dirPath);

// 添加权限:允许用户“Domain\User”读取权限
dirSecurity.AddAccessRule(new FileSystemAccessRule(
    "Domain\\User",
    FileSystemRights.Read,
    AccessControlType.Allow));

// 删除权限:移除用户“Domain\User”的所有权限
AuthorizationRuleCollection rules = dirSecurity.GetAccessRules(true, true, typeof(NTAccount));
foreach (FileSystemAccessRule rule in rules)
{
    if (rule.IdentityReference.Value == "Domain\\User")
    {
        dirSecurity.RemoveAccessRule(rule);
    }
}

Directory.SetAccessControl(dirPath, dirSecurity);

说明
- 使用 GetAccessRules() 获取所有访问规则。
- 可以通过遍历规则并使用 RemoveAccessRule() 删除特定规则。
- 修改权限后必须调用 SetAccessControl() 才能生效。

5.3 异步获取目录内容(GetDirectoriesAsync)

在图形界面应用程序中,同步操作可能会导致界面冻结,因此推荐使用异步方法处理目录遍历。

5.3.1 异步操作的基本原理

虽然.NET原生的 DirectoryInfo 类没有提供异步方法,但我们可以通过 Task.Run() 封装同步方法实现异步操作。

public async Task<List<string>> GetDirectoriesAsync(string path)
{
    return await Task.Run(() =>
    {
        try
        {
            return Directory.GetDirectories(path).ToList();
        }
        catch (Exception ex)
        {
            Console.WriteLine("访问出错:" + ex.Message);
            return new List<string>();
        }
    });
}

5.3.2 在UI应用中避免界面冻结

在WPF或WinForms中使用时,可以结合 await 避免阻塞UI线程:

private async void btnLoad_Click(object sender, EventArgs e)
{
    string path = txtPath.Text;
    var dirs = await GetDirectoriesAsync(path);
    foreach (var dir in dirs)
    {
        lstDirectories.Items.Add(dir);
    }
}

说明
- 使用 await 确保异步执行不会阻塞主线程。
- UI控件的更新应在主线程中执行,避免跨线程异常。

5.4 完整示例代码演示目录遍历

我们来构建一个完整的控制台示例,演示如何递归遍历目录并输出所有文件。

5.4.1 控制台应用程序的实现

static void Main(string[] args)
{
    string rootPath = @"C:\SampleData";
    TraverseDirectory(rootPath);
}

static void TraverseDirectory(string path)
{
    try
    {
        foreach (string dir in Directory.GetDirectories(path))
        {
            Console.WriteLine("目录:" + dir);
            TraverseDirectory(dir); // 递归调用
        }

        foreach (string file in Directory.GetFiles(path))
        {
            Console.WriteLine("  文件:" + file);
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"访问{path}失败:{ex.Message}");
    }
}

说明
- 使用递归方式遍历所有子目录。
- 异常处理确保访问失败时程序不崩溃。

5.4.2 图形界面中展示目录结构

在WPF中可以使用 TreeView 控件展示目录结构:

<TreeView x:Name="treeView" />
<Button Content="加载目录" Click="LoadDirectory_Click" />
private async void LoadDirectory_Click(object sender, RoutedEventArgs e)
{
    var root = new TreeViewItem { Header = "C:\\" };
    await BuildTreeAsync(root, "C:\\");
    treeView.Items.Add(root);
}

private async Task BuildTreeAsync(TreeViewItem parent, string path)
{
    var dirs = await GetDirectoriesAsync(path);
    foreach (var dir in dirs)
    {
        var item = new TreeViewItem { Header = Path.GetFileName(dir) };
        parent.Items.Add(item);
        await BuildTreeAsync(item, dir);
    }
}

说明
- 使用 TreeViewItem 动态构建目录树。
- 每个节点递归加载其子节点。

5.4.3 日志记录与用户交互设计

在实际项目中,建议将操作记录输出到日志文件中,便于后期分析:

public static void Log(string message)
{
    string logPath = @"C:\Logs\app.log";
    string logEntry = $"{DateTime.Now}: {message}{Environment.NewLine}";
    File.AppendAllText(logPath, logEntry);
}

说明
- File.AppendAllText() 方法用于追加日志内容。
- 可扩展为使用NLog、Serilog等日志框架。

5.5 C#文件处理在实际开发中的应用

5.5.1 数据备份与同步工具开发

利用 DirectoryInfo FileInfo 可以开发自动备份工具,例如:

  • 定期扫描源目录中的文件
  • 比较目标目录中的文件时间戳
  • 复制更新过的文件并记录日志

5.5.2 文件扫描与病毒检测系统设计

结合病毒扫描引擎API,可以实现:

  • 遍历指定目录下的所有文件
  • 调用第三方API进行文件扫描
  • 对感染文件进行隔离或删除处理

5.5.3 实际项目中的最佳实践与经验分享

  • 路径处理 :始终使用 Path.Combine() Path 类方法,避免硬编码路径分隔符。
  • 权限控制 :在部署前测试目标目录的权限设置,避免运行时访问失败。
  • 异步处理 :对于大型目录结构,使用异步加载和并行处理提升性能。
  • 日志记录 :关键操作务必记录日志,便于排查问题。
  • 异常处理 :对 DirectoryNotFoundException UnauthorizedAccessException 等常见异常进行捕获和处理。
graph TD
    A[开始目录遍历] --> B[读取当前目录]
    B --> C{是否有子目录?}
    C -->|是| D[递归遍历子目录]
    C -->|否| E[读取当前目录文件]
    E --> F[输出文件列表]
    D --> G[输出目录结构]
    G --> H[结束]

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:在C#开发中,获取本地磁盘目录并处理文件是常见的基础操作,涉及 System.IO 命名空间中的 DirectoryInfo 、文件遍历、递归处理、权限控制等内容。本实例源码详细演示了如何使用C#实现磁盘目录的获取与遍历,包含同步与异步方法、LINQ查询、异常处理、路径操作等关键技术点。通过完整示例代码,开发者可以快速掌握文件系统的操作技巧,并应用于实际项目中。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

更多推荐