当前位置: 首页 > news >正文

【.Net实用方法总结】 整理并总结文件和流、异步文件IO、处理IO错误

CSDN话题挑战赛第2期
参赛话题:学习笔记

🐋作者简介:博主是一位.Net开发者,同时也是RPA和低代码平台的践行者。
🐬个人主页:会敲键盘的肘子
🐰系列专栏:.Net实用方法总结
🦀专栏简介:博主针对.Net开发和C站问答过程中遇到的问题进行总结,形成本专栏,希望可以帮助到您解决问题。
🐶座右铭:总有一天你所坚持的会反过来拥抱你。


在这里插入图片描述

🌈写在前面:

文件和流 I/O(输入/输出)是指在存储媒介中传入或传出数据。 在 .NET 中,System.IO 命名空间包含允许以异步方式和同步方式对数据流和文件进行读取和写入操作的类型。 这些命名空间还包含对文件执行压缩和解压缩的类型,以及通过管道和串行端口启用通信的类型。


👉本文关键字:System.IO、文件系统、方法示例、异步IO

文章目录

      • 1️⃣ 概述
      • 2️⃣ 通用 I/O 任务
        • ♈ 通用文件任务
        • ♊ 通用目录任务
      • 3️⃣ 如何:复制目录
        • ♈ 场景需求
        • ♊ 解决方案
      • 4️⃣ 如何:枚举目录和文件
        • ♈ 场景需求
        • ♊ 解决方案
          • ⭐ 示例:使用目录类
          • ⭐ 示例:使用目录类
      • 5️⃣ 如何:对新建的数据文件进行读取和写入
        • ♈ 场景需求
        • ♊ 解决方案
      • 6️⃣ 如何:打开并追加到日志文件
        • ♈ 场景需求
        • ♊ 解决方案
      • 7️⃣ 如何:将文本写入文件
        • ♈ 场景需求
        • ♊ 解决方案
          • ⭐ 示例:使用 StreamWriter 同步写入文本
          • ⭐ 示例:使用 StreamWriter 同步追加文本
          • ⭐ 示例:使用 StreamWriter 异步写入文本
          • ⭐ 示例:使用文件类编写和追加文本
      • 8️⃣ 如何:从文件中读取文本
        • ♈ 场景需求
        • ♊ 解决方案
          • ⭐ 示例:控制台应用中的同步读取
          • ⭐ 示例:WPF 应用中的异步读取
      • 9️⃣ 如何:从字符串中读取字符
        • ♈ 场景需求
        • ♊ 解决方案
          • ⭐ 示例:同步读取字符
          • ⭐ 示例:异步读取字符
      • 1️⃣0️⃣ 如何:向字符串写入字符
        • ♈ 场景需求
        • ♊ 解决方案
          • ⭐ 示例:在控制台应用中以同步方式编写字符
          • ⭐ 示例:在 WPF 应用中以同步方式编写字符
      • 1️⃣1️⃣ 异步文件 I/O
        • ♈ 场景需求
        • ♊ 解决方案
          • ⭐ 示例
      • 1️⃣2️⃣ 处理 .NET 中的 I/O 错误
        • ♈ I/O 操作中的异常处理
        • ♊ 处理 IOException
          • ⭐ 示例

1️⃣ 概述

文件和流 I/O(输入/输出)是指在存储媒介中传入或传出数据。 在 .NET 中,System.IO 命名空间包含允许以异步方式和同步方式对数据流和文件进行读取和写入操作的类型。 这些命名空间还包含对文件执行压缩和解压缩的类型,以及通过管道和串行端口启用通信的类型。

文件是一个由字节组成的有序的命名集合,它具有永久存储。 在处理文件时,你将处理目录路径、磁盘存储、文件和目录名称。 相反,流是一个字节序列,可用于对后备存储进行读取和写入操作,后备存储可以是多个存储媒介之一(例如,磁盘或内存)。 正如存在除磁盘之外的多种后备存储一样,也存在除文件流之外的多种流(如网络、内存和管道流)。

  • 文件和目录

    • File - 提供用于创建、复制、删除、移动和打开文件的静态方法,并可帮助创建 FileStream 对象。
    • FileInfo - 提供用于创建、复制、删除、移动和打开文件的实例方法,并可帮助创建 FileStream 对象。
    • Directory - 提供用于创建、移动和枚举目录和子目录的静态方法。
    • DirectoryInfo - 提供用于创建、移动和枚举目录和子目录的实例方法。
    • Path - 提供用于以跨平台的方式处理目录字符串的方法和属性。
    • FileStream - 用于对文件进行读取和写入操作。
    • MemoryStream - 用于作为后备存储对内存进行读取和写入操作。
    • BufferedStream - 用于改进读取和写入操作的性能。
  • 读取器和编写器

    • BinaryReader 和 BinaryWriter - 用于将基元数据类型作为二进制值进行读取和写入。
    • StreamReader 和 StreamWriter - 用于通过使用编码值在字符和字节之间来回转换来读取和写入字符。
    • StringReader 和 StringWriter - 用于从字符串读取字符以及将字符写入字符串中。
    • TextReader 和 TextWriter - 用作其他读取器和编写器(读取和写入字符和字符串,而不是二进制数据)的抽象基类。

2️⃣ 通用 I/O 任务

♈ 通用文件任务

若要执行此操作…请参见本主题中的示例…
创建文本文件File.CreateText 方法 FileInfo.CreateText 方法 File.Create 方法 FileInfo.Create 方法
写入到文本文件如何:将文本写入文件 如何:编写文本文件 (C++/CLI)
从文本文件读取如何:从文件中读取文本
向文件中追加文本如何:打开并追加到日志文件 File.AppendText 方法 FileInfo.AppendText 方法
重命名或移动文件File.Move 方法 FileInfo.MoveTo 方法
删除文件File.Delete 方法 FileInfo.Delete 方法
复制文件File.Copy 方法 FileInfo.CopyTo 方法
获取文件大小FileInfo.Length 属性
获取文件特性File.GetAttributes 方法
设置文件特性File.SetAttributes 方法
确定文件是否存在File.Exists 方法
从二进制文件读取如何:对新建的数据文件进行读取和写入
写入二进制文件如何:对新建的数据文件进行读取和写入
检索文件扩展名Path.GetExtension 方法
检索文件的完全限定路径Path.GetFullPath 方法
检索路径中的文件名和扩展名Path.GetFileName 方法
更改文件扩展名Path.ChangeExtension 方法

♊ 通用目录任务

若要执行此操作…请参见本主题中的示例…
访问特定文件夹(如“My Documents”)中的文件如何:将文本写入文件
创建目录Directory.CreateDirectory 方法 FileInfo.Directory 属性
创建子目录DirectoryInfo.CreateSubdirectory 方法
重命名或移动目录Directory.Move 方法 DirectoryInfo.MoveTo 方法
复制目录如何:复制目录
删除目录Directory.Delete 方法 DirectoryInfo.Delete 方法
查看目录中的文件和子目录如何:枚举目录和文件
查明目录大小System.IO.Directory 类
确定目录是否存在Directory.Exists 方法

3️⃣ 如何:复制目录

♈ 场景需求

本文演示如何使用 I/O 类将目录下的内容同步复制到另一个位置。

有关异步文件复制的示例,请参阅异步文件 I/O。

此示例通过将 CopyDirectory 方法的 recursive 参数设置为 true 来复制子目录。 CopyDirectory 方法通过对每个子目录调用其自身的方法来递归复制它们,直到再也没有子目录可以复制为止。

♊ 解决方案

示例

using System.IO;

CopyDirectory(@".\", @".\copytest", true);

static void CopyDirectory(string sourceDir, string destinationDir, bool recursive)
{
    // Get information about the source directory
    var dir = new DirectoryInfo(sourceDir);

    // Check if the source directory exists
    if (!dir.Exists)
        throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}");

    // Cache directories before we start copying
    DirectoryInfo[] dirs = dir.GetDirectories();

    // Create the destination directory
    Directory.CreateDirectory(destinationDir);

    // Get the files in the source directory and copy to the destination directory
    foreach (FileInfo file in dir.GetFiles())
    {
        string targetFilePath = Path.Combine(destinationDir, file.Name);
        file.CopyTo(targetFilePath);
    }

    // If recursive and copying subdirectories, recursively call this method
    if (recursive)
    {
        foreach (DirectoryInfo subDir in dirs)
        {
            string newDestinationDir = Path.Combine(destinationDir, subDir.Name);
            CopyDirectory(subDir.FullName, newDestinationDir, true);
        }
    }
}

4️⃣ 如何:枚举目录和文件

♈ 场景需求

在处理目录和文件的大型集合时,可枚举的集合能够比数组提供更好的性能。 要枚举目录和文件,请使用可返回目录和文件名的可枚举集合的方法或其 DirectoryInfo、FileInfo 或 FileSystemInfo 对象。

如果只想搜索并返回目录名称或文件名,请使用 Directory 类的枚举方法。 若要搜索并返回目录或文件的其他属性,请使用 DirectoryInfo 和 FileSystemInfo 类。

可以使用这些方法中的可枚举集合作为集合类(如 List)的构造函数的 IEnumerable 参数。

下表总结了返回可枚举的文件和目录集合的方法:

搜索并返回使用方法
目录名称Directory.EnumerateDirectories
目录信息 (DirectoryInfo)DirectoryInfo.EnumerateDirectories
文件名Directory.EnumerateFiles
文件信息 (FileInfo)DirectoryInfo.EnumerateFiles
文件系统条目名称Directory.EnumerateFileSystemEntries
文件系统条目信息 (FileSystemInfo)DirectoryInfo.EnumerateFileSystemInfos
目录和文件名称Directory.EnumerateFileSystemEntries

♊ 解决方案

⭐ 示例:使用目录类

以下示例使用 Directory.EnumerateDirectories(String) 方法获取指定路径中顶级目录名称的列表。

using System;
using System.Collections.Generic;
using System.IO;

class Program
{
    private static void Main(string[] args)
    {
        try
        {
            // Set a variable to the My Documents path.
            string docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

            List<string> dirs = new List<string>(Directory.EnumerateDirectories(docPath));

            foreach (var dir in dirs)
            {
                Console.WriteLine($"{dir.Substring(dir.LastIndexOf(Path.DirectorySeparatorChar) + 1)}");
            }
            Console.WriteLine($"{dirs.Count} directories found.");
        }
        catch (UnauthorizedAccessException ex)
        {
            Console.WriteLine(ex.Message);
        }
        catch (PathTooLongException ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

以下示例使用 Directory.EnumerateFiles(String, String, SearchOption) 方法递归枚举目录中的所有文件名以及与特定模式匹配的子目录。 然后它读取每个文件的每一行,并显示包含指定字符串的行及其文件名和路径。

using System;
using System.IO;
using System.Linq;

class Program
{
    static void Main(string[] args)
    {
        try
        {
            // Set a variable to the My Documents path.
            string docPath =
            Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

            var files = from file in Directory.EnumerateFiles(docPath, "*.txt", SearchOption.AllDirectories)
                        from line in File.ReadLines(file)
                        where line.Contains("Microsoft")
                        select new
                        {
                            File = file,
                            Line = line
                        };

            foreach (var f in files)
            {
                Console.WriteLine($"{f.File}\t{f.Line}");
            }
            Console.WriteLine($"{files.Count().ToString()} files found.");
        }
        catch (UnauthorizedAccessException uAEx)
        {
            Console.WriteLine(uAEx.Message);
        }
        catch (PathTooLongException pathEx)
        {
            Console.WriteLine(pathEx.Message);
        }
    }
}
⭐ 示例:使用目录类

下面的示例使用 DirectoryInfo.EnumerateDirectories 方法列出顶级目录的集合,这些顶级目录的 CreationTimeUtc 早于某个 DateTime 值。

using System;
using System.IO;

namespace EnumDir
{
    class Program
    {
        static void Main(string[] args)
        {
            // Set a variable to the Documents path.
            string docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

            DirectoryInfo dirPrograms = new DirectoryInfo(docPath);
            DateTime StartOf2009 = new DateTime(2009, 01, 01);

            var dirs = from dir in dirPrograms.EnumerateDirectories()
            where dir.CreationTimeUtc > StartOf2009
            select new
            {
                ProgDir = dir,
            };

            foreach (var di in dirs)
            {
                Console.WriteLine($"{di.ProgDir.Name}");
            }
        }
    }
}
// </Snippet1>

下例使用 DirectoryInfo.EnumerateFiles 方法列出 Length 超过 10MB 的所有文件。 此示例先枚举顶级目录以捕获可能的未授权访问异常,再枚举文件。

using System;
using System.IO;

class Program
{
    static void Main(string[] args)
    {
        // Set a variable to the My Documents path.
        string docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

        DirectoryInfo diTop = new DirectoryInfo(docPath);

        try
        {
            foreach (var fi in diTop.EnumerateFiles())
            {
                try
                {
                    // Display each file over 10 MB;
                    if (fi.Length > 10000000)
                    {
                        Console.WriteLine($"{fi.FullName}\t\t{fi.Length.ToString("N0")}");
                    }
                }
                catch (UnauthorizedAccessException unAuthTop)
                {
                    Console.WriteLine($"{unAuthTop.Message}");
                }
            }

            foreach (var di in diTop.EnumerateDirectories("*"))
            {
                try
                {
                    foreach (var fi in di.EnumerateFiles("*", SearchOption.AllDirectories))
                    {
                        try
                        {
                            // Display each file over 10 MB;
                            if (fi.Length > 10000000)
                            {
                                Console.WriteLine($"{fi.FullName}\t\t{fi.Length.ToString("N0")}");
                            }
                        }
                        catch (UnauthorizedAccessException unAuthFile)
                        {
                            Console.WriteLine($"unAuthFile: {unAuthFile.Message}");
                        }
                    }
                }
                catch (UnauthorizedAccessException unAuthSubDir)
                {
                    Console.WriteLine($"unAuthSubDir: {unAuthSubDir.Message}");
                }
            }
        }
        catch (DirectoryNotFoundException dirNotFound)
        {
            Console.WriteLine($"{dirNotFound.Message}");
        }
        catch (UnauthorizedAccessException unAuthDir)
        {
            Console.WriteLine($"unAuthDir: {unAuthDir.Message}");
        }
        catch (PathTooLongException longPath)
        {
            Console.WriteLine($"{longPath.Message}");
        }
    }
}

5️⃣ 如何:对新建的数据文件进行读取和写入

♈ 场景需求

System.IO.BinaryWriter 和 System.IO.BinaryReader 类用于写入和读取字符串以外的数据。 下面的示例演示如何创建空文件流,向其写入数据并从中读取数据。

示例将在当前目录中创建名为 Test.data 的数据文件,也就同时创建了相关的 BinaryWriter 和 BinaryReader 对象,并且 BinaryWriter 对象用于向 Test.data 写入整数 0 到 10,这会将文件指针置于文件末尾。 BinaryReader 对象将文件指针设置回原始位置并读取指定的内容。

♊ 解决方案

示例

using System;
using System.IO;

class MyStream
{
    private const string FILE_NAME = "Test.data";

    public static void Main()
    {
        if (File.Exists(FILE_NAME))
        {
            Console.WriteLine($"{FILE_NAME} already exists!");
            return;
        }

        using (FileStream fs = new FileStream(FILE_NAME, FileMode.CreateNew))
        {
            using (BinaryWriter w = new BinaryWriter(fs))
            {
                for (int i = 0; i < 11; i++)
                {
                    w.Write(i);
                }
            }
        }

        using (FileStream fs = new FileStream(FILE_NAME, FileMode.Open, FileAccess.Read))
        {
            using (BinaryReader r = new BinaryReader(fs))
            {
                for (int i = 0; i < 11; i++)
                {
                    Console.WriteLine(r.ReadInt32());
                }
            }
        }
    }
}


// The example creates a file named "Test.data" and writes the integers 0 through 10 to it in binary format.
// It then writes the contents of Test.data to the console with each integer on a separate line.

6️⃣ 如何:打开并追加到日志文件

♈ 场景需求

StreamWriter 和 StreamReader 对流执行字符写入和读取操作。 下面的代码示例打开 log.txt 文件以供输入,或创建该文件(如果尚无文件的话),并将日志信息追加到文件末尾。 然后,示例将文件内容写入标准输出以供显示。

作为此示例的替换方法,可以将信息存储为一个字符串或字符串数组,并使用 File.WriteAllText 或 File.WriteAllLines 方法实现相同的功能。

♊ 解决方案

示例

using System;
using System.IO;

class DirAppend
{
    public static void Main()
    {
        using (StreamWriter w = File.AppendText("log.txt"))
        {
            Log("Test1", w);
            Log("Test2", w);
        }

        using (StreamReader r = File.OpenText("log.txt"))
        {
            DumpLog(r);
        }
    }

    public static void Log(string logMessage, TextWriter w)
    {
        w.Write("\r\nLog Entry : ");
        w.WriteLine($"{DateTime.Now.ToLongTimeString()} {DateTime.Now.ToLongDateString()}");
        w.WriteLine("  :");
        w.WriteLine($"  :{logMessage}");
        w.WriteLine ("-------------------------------");
    }

    public static void DumpLog(StreamReader r)
    {
        string line;
        while ((line = r.ReadLine()) != null)
        {
            Console.WriteLine(line);
        }
    }
}
// The example creates a file named "log.txt" and writes the following lines to it,
// or appends them to the existing "log.txt" file:

// Log Entry : <current long time string> <current long date string>
//  :
//  :Test1
// -------------------------------

// Log Entry : <current long time string> <current long date string>
//  :
//  :Test2
// -------------------------------

// It then writes the contents of "log.txt" to the console.

7️⃣ 如何:将文本写入文件

♈ 场景需求

本主题介绍将文本写入 .NET 应用文件的不同方法。

下面的类和方法通常用于将文本写入文件:

  • StreamWriter 包含同步写入文件的方法(Write 或 WriteLine)或者异步写入文件的方法(WriteAsync 和 WriteLineAsync)。
  • File 提供了将文本写入文件的静态方法(例如,WriteAllLines 和 WriteAllText),或者向文件中追加文本的静态方法(例如,AppendAllLines、AppendAllText 和 AppendText)。
  • Path 适用于包含文件或目录路径信息的字符串。 它包含 Combine 方法,在 .NET Core 2.1 及更高版本中包含 Join 和 TryJoin 方法,允许串联字符串以生成文件或目录路径。

♊ 解决方案

⭐ 示例:使用 StreamWriter 同步写入文本

以下示例演示如何使用 StreamWriter 类,一次一行同步地将文本写入新文件。 因为在 StreamWriter 语句中已声明并实例化 using 对象,所以会调用自动刷新并关闭流的 Dispose 方法。

using System;
using System.IO;

class Program
{
    static void Main(string[] args)
    {

        // Create a string array with the lines of text
        string[] lines = { "First line", "Second line", "Third line" };

        // Set a variable to the Documents path.
        string docPath =
          Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

        // Write the string array to a new file named "WriteLines.txt".
        using (StreamWriter outputFile = new StreamWriter(Path.Combine(docPath, "WriteLines.txt")))
        {
            foreach (string line in lines)
                outputFile.WriteLine(line);
        }
    }
}
// The example creates a file named "WriteLines.txt" with the following contents:
// First line
// Second line
// Third line
⭐ 示例:使用 StreamWriter 同步追加文本

以下示例演示如何使用 StreamWriter 类以同步方式将文本追加到第一个示例中创建的文本文件。

using System;
using System.IO;

class Program
{
    static void Main(string[] args)
    {

        // Set a variable to the Documents path.
        string docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

        // Append text to an existing file named "WriteLines.txt".
        using (StreamWriter outputFile = new StreamWriter(Path.Combine(docPath, "WriteLines.txt"), true))
        {
            outputFile.WriteLine("Fourth Line");
        }
    }
}
// The example adds the following line to the contents of "WriteLines.txt":
// Fourth Lineclass WriteAllText
{
    public static async Task ExampleAsync()
    {
        string text =
            "A class is the most powerful data type in C#. Like a structure, " +
            "a class defines the data and behavior of the data type. ";

        await File.WriteAllTextAsync("WriteText.txt", text);
    }
}
⭐ 示例:使用 StreamWriter 异步写入文本

下面的示例演示如何使用 StreamWriter 类异步地将文本写入新文件。 要调用 WriteAsync 方法,方法调用必须在 async 方法内。 C# 示例需要 C# 7.1或更高版本,这会在对程序入口点上增加对 async 修饰符的支持。

using System;
using System.IO;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        // Set a variable to the Documents path.
        string docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

        // Write the specified text asynchronously to a new file named "WriteTextAsync.txt".
        using (StreamWriter outputFile = new StreamWriter(Path.Combine(docPath, "WriteTextAsync.txt")))
        {
            await outputFile.WriteAsync("This is a sentence.");
        }
    }
}
// The example creates a file named "WriteTextAsync.txt" with the following contents:
// This is a sentence.
⭐ 示例:使用文件类编写和追加文本

下面的示例演示如何使用 File 类将文本写入新文件并将新的文本行追加到同一文件。 WriteAllText 和 AppendAllLines 方法会自动打开和关闭文件。 如果提供给 WriteAllText 方法的路径已存在,则覆盖该文件。

using System;
using System.IO;

class Program
{
    static void Main(string[] args)
    {
        // Create a string with a line of text.
        string text = "First line" + Environment.NewLine;

        // Set a variable to the Documents path.
        string docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

        // Write the text to a new file named "WriteFile.txt".
        File.WriteAllText(Path.Combine(docPath, "WriteFile.txt"), text);

        // Create a string array with the additional lines of text
        string[] lines = { "New line 1", "New line 2" };

        // Append new lines of text to the file
        File.AppendAllLines(Path.Combine(docPath, "WriteFile.txt"), lines);
    }
}
// The example creates a file named "WriteFile.txt" with the contents:
// First line
// And then appends the following contents:
// New line 1
// New line 2

8️⃣ 如何:从文件中读取文本

♈ 场景需求

下面的示例演示如何使用适用于桌面应用的 .NET 以异步方式和同步方式从文本文件中读取文本。 在这两个示例中,当你创建 StreamReader 类的实例时,你会提供文件的绝对路径或相对路径。

♊ 解决方案

⭐ 示例:控制台应用中的同步读取

以下示例演示控制台应用中的同步读取操作。 此示例使用流读取器打开文本文件,将内容复制到字符串并将字符串输出到控制台。

using System;
using System.IO;

class Program
{
    public static void Main()
    {
        try
        {
            // Open the text file using a stream reader.
            using (var sr = new StreamReader("TestFile.txt"))
            {
                // Read the stream as a string, and write the string to the console.
                Console.WriteLine(sr.ReadToEnd());
            }
        }
        catch (IOException e)
        {
            Console.WriteLine("The file could not be read:");
            Console.WriteLine(e.Message);
        }
    }
}
⭐ 示例:WPF 应用中的异步读取

以下示例演示 Windows Presentation Foundation (WPF) 应用中的异步读取操作。

using System.IO;
using System.Windows;

namespace TextFiles;

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    public MainWindow() => InitializeComponent();

    private async void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        try
        {
            using (var sr = new StreamReader("TestFile.txt"))
            {
                ResultBlock.Text = await sr.ReadToEndAsync();
            }
        }
        catch (FileNotFoundException ex)
        {
            ResultBlock.Text = ex.Message;
        }
    }
}

9️⃣ 如何:从字符串中读取字符

♈ 场景需求

下面的代码示例展示了如何从字符串中异步或同步读取字符。

♊ 解决方案

⭐ 示例:同步读取字符

此示例从字符串中同步读取 13 个字符,将它们存储到数组中,并显示这些字符。 然后,示例将读取字符串中的剩余字符,将它们存储到数组中(从第六个元素开始),并显示数组的内容。

using System;
using System.IO;

public class CharsFromStr
{
    public static void Main()
    {
        string str = "Some number of characters";
        char[] b = new char[str.Length];

        using (StringReader sr = new StringReader(str))
        {
            // Read 13 characters from the string into the array.
            sr.Read(b, 0, 13);
            Console.WriteLine(b);

            // Read the rest of the string starting at the current string position.
            // Put in the array starting at the 6th array member.
            sr.Read(b, 5, str.Length - 13);
            Console.WriteLine(b);
        }
    }
}
// The example has the following output:
//
// Some number o
// Some f characters
⭐ 示例:异步读取字符

下一个示例是 WPF 应用背后的代码。 在窗口加载时,示例从 TextBox 控件异步读取所有字符,并将其存储在数组中。 随后,它以异步方式将每个字母或空格字符写入单独的 TextBlock 控件行。

using System;
using System.Text;
using System.Windows;
using System.IO;

namespace StringReaderWriter
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private async void Window_Loaded(object sender, RoutedEventArgs e)
        {
            char[] charsRead = new char[UserInput.Text.Length];
            using (StringReader reader = new StringReader(UserInput.Text))
            {
                await reader.ReadAsync(charsRead, 0, UserInput.Text.Length);
            }

            StringBuilder reformattedText = new StringBuilder();
            using (StringWriter writer = new StringWriter(reformattedText))
            {
                foreach (char c in charsRead)
                {
                    if (char.IsLetter(c) || char.IsWhiteSpace(c))
                    {
                        await writer.WriteLineAsync(char.ToLower(c));
                    }
                }
            }
            Result.Text = reformattedText.ToString();
        }
    }
}

1️⃣0️⃣ 如何:向字符串写入字符

♈ 场景需求

下面的代码示例从字符数组以同步或异步方式向字符串写入字符。

♊ 解决方案

⭐ 示例:在控制台应用中以同步方式编写字符

下面的示例使用 StringWriter 将五个字符同步写入 StringBuilder 对象。

using System;
using System.IO;
using System.Text;

public class CharsToStr
{
    public static void Main()
    {
        StringBuilder sb = new StringBuilder("Start with a string and add from ");
        char[] b = { 'c', 'h', 'a', 'r', '.', ' ', 'B', 'u', 't', ' ', 'n', 'o', 't', ' ', 'a', 'l', 'l' };

        using (StringWriter sw = new StringWriter(sb))
        {
            // Write five characters from the array into the StringBuilder.
            sw.Write(b, 0, 5);
            Console.WriteLine(sb);
        }
    }
}
// The example has the following output:
//
// Start with a string and add from char.
⭐ 示例:在 WPF 应用中以同步方式编写字符

下一个示例是 WPF 应用背后的代码。 在窗口加载时,示例从 TextBox 控件异步读取所有字符,并将其存储在数组中。 随后,它以异步方式将每个字母或空格字符写入单独的 TextBlock 控件行。

using System;
using System.Text;
using System.Windows;
using System.IO;

namespace StringReaderWriter
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private async void Window_Loaded(object sender, RoutedEventArgs e)
        {
            char[] charsRead = new char[UserInput.Text.Length];
            using (StringReader reader = new StringReader(UserInput.Text))
            {
                await reader.ReadAsync(charsRead, 0, UserInput.Text.Length);
            }

            StringBuilder reformattedText = new StringBuilder();
            using (StringWriter writer = new StringWriter(reformattedText))
            {
                foreach (char c in charsRead)
                {
                    if (char.IsLetter(c) || char.IsWhiteSpace(c))
                    {
                        await writer.WriteLineAsync(char.ToLower(c));
                    }
                }
            }
            Result.Text = reformattedText.ToString();
        }
    }
}

1️⃣1️⃣ 异步文件 I/O

♈ 场景需求

异步操作使您能在不阻塞主线程的情况下执行占用大量资源的 I/O 操作。 在 Windows 8.x 应用商店应用或桌面应用中一个耗时的流操作可能阻塞 UI 线程并让应用看起来好像不工作时,这种性能的考虑就显得尤为重要了。

♊ 解决方案

从 .NET Framework 4.5 开始,I/O 类型包括了异步方法,以简化异步操作。 异步方法在其名称中包括 Async ,例如 ReadAsync、 WriteAsync、 CopyToAsync、 FlushAsync、 ReadLineAsync和 ReadToEndAsync。 这些异步方法基于流类(例如 Stream、 FileStream和 MemoryStream)和用来向流中读出或写入数据的类(例如 TextReader 和 TextWriter)实现。

在 .NET Framework 4 和更早的版本中,你必须使用 BeginRead 和 EndRead 等方法来实现异步 I/O 操作。 这些方法仍然在当前 .NET 版本中可用,从而支持传统的代码;但是,异步方法能帮助你更轻松地实现异步 I/O 操作。

C# 和 Visual Basic 分别具有两个用于异步编程的关键字:

  • Async (Visual Basic) 或 async (C#) 修饰符,您可以用来标记包含异步操作的方法。
  • Await (Visual Basic) 或 await (C#) 运算符,可以应用到异步方法的结果中。

如下面的示例所示,若要实现异步 I/O 操作,请把这些关键字和异步方法结合使用。 有关详细信息,请参阅使用 async 和 await 的异步编程 (C#) 或 使用 Async 和 Await 的异步编程 (Visual Basic)。

下面的示例演示如何使用两个 FileStream 对象把文件从一个目录异步复制到另一个目录。 需要注意 Click 控件的 Button 事件处理程序具有 async 修饰符标记,因为它调用异步方法。

⭐ 示例
using System;
using System.Threading.Tasks;
using System.Windows;
using System.IO;

namespace WpfApplication
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            string startDirectory = @"c:\Users\exampleuser\start";
            string endDirectory = @"c:\Users\exampleuser\end";

            foreach (string filename in Directory.EnumerateFiles(startDirectory))
            {
                using (FileStream sourceStream = File.Open(filename, FileMode.Open))
                {
                    using (FileStream destinationStream = File.Create(Path.Combine(endDirectory, Path.GetFileName(filename))))
                    {
                        await sourceStream.CopyToAsync(destinationStream);
                    }
                }
            }
        }
    }
}

下一个例子类似于前面的例子,但是使用 StreamReader 和 StreamWriter 对象以异步方式读取和写入文本文件的内容。

private async void Button_Click(object sender, RoutedEventArgs e)
{
    string UserDirectory = @"c:\Users\exampleuser\";

    using (StreamReader SourceReader = File.OpenText(UserDirectory + "BigFile.txt"))
    {
        using (StreamWriter DestinationWriter = File.CreateText(UserDirectory + "CopiedFile.txt"))
        {
            await CopyFilesAsync(SourceReader, DestinationWriter);
        }
    }
}

public async Task CopyFilesAsync(StreamReader Source, StreamWriter Destination)
{
    char[] buffer = new char[0x1000];
    int numRead;
    while ((numRead = await Source.ReadAsync(buffer, 0, buffer.Length)) != 0)
    {
        await Destination.WriteAsync(buffer, 0, numRead);
    }
}

下一个示例演示用于在 Windows 8.x 应用商店应用中以 Stream 的形式打开文件的代码隐藏文件和 XAML 文件,并且通过使用 StreamReader 类的实例来读取其内容。 它使用异步方法以流的形式打开文件并读取其内容。

using System;
using System.IO;
using System.Text;
using Windows.Storage.Pickers;
using Windows.Storage;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace ExampleApplication
{
    public sealed partial class BlankPage : Page
    {
        public BlankPage()
        {
            this.InitializeComponent();
        }

        private async void Button_Click_1(object sender, RoutedEventArgs e)
        {
            StringBuilder contents = new StringBuilder();
            string nextLine;
            int lineCounter = 1;

            var openPicker = new FileOpenPicker();
            openPicker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
            openPicker.FileTypeFilter.Add(".txt");
            StorageFile selectedFile = await openPicker.PickSingleFileAsync();

            using (StreamReader reader = new StreamReader(await selectedFile.OpenStreamForReadAsync()))
            {
                while ((nextLine = await reader.ReadLineAsync()) != null)
                {
                    contents.AppendFormat("{0}. ", lineCounter);
                    contents.Append(nextLine);
                    contents.AppendLine();
                    lineCounter++;
                    if (lineCounter > 3)
                    {
                        contents.AppendLine("Only first 3 lines shown.");
                        break;
                    }
                }
            }
            DisplayContentsBlock.Text = contents.ToString();
        }
    }
}

1️⃣2️⃣ 处理 .NET 中的 I/O 错误

♈ I/O 操作中的异常处理

由于操作系统的这一依赖性,相同异常条件(例如在我们的示例中没有发现错误目录)可能会导致 I/O 方法引发任何一种 I/O 异常。 这意味着,在调用 I/O API 时,代码应准备好处理大多数或者所有这些异常,如下表所示:

异常类型.NET Core/.NET 5+.NET Framework
IOException
FileNotFoundException
DirectoryNotFoundException
DriveNotFoundException
PathTooLongException
OperationCanceledException
UnauthorizedAccessException
ArgumentException.NET core 2.0 及早期版本
NotSupportedException
SecurityException仅受限的信任

♊ 处理 IOException

作为 System.IO 命名空间中异常的基类,当任何错误代码未映射到预定义的异常类型时,也将引发 IOException。 这意味着异常可以由任何 I/O 操作引发。

因为 IOException 是 System.IO 命名空间中其他异常类型的基类,应在处理其他 I/O 相关异常后处理 catch 块。

此外,从 .NET Core 2.1 开始,已删除对路径正确性(例如,为了确保路径中不存在无效字符)的验证检查,且运行时会引发从操作系统错误代码(而非从它自己的验证代码)映射的异常。 在这种情况下,最有可能引发的异常是 IOException,虽然也可能引发任何其他异常类型。

请注意,在异常处理代码中,应始终最后处理 IOException。 否则,因为它是所有其他 IO 异常的基类,将不会评估派生类的 catch 块。

在 IOException 情况下,可以从 IOException.HResult 属性获取更多错误信息。 若要将 HResult 值转换为 Win32 错误代码,可以删除 32 位值的前 16 位。 下表列出了可能包装在 IOException 中的错误代码。

HResult返回的常量描述
ERROR_SHARING_VIOLATION32缺少文件名称,或文件或目录正在使用中。
ERROR_FILE_EXISTS80该文件已存在。
ERROR_INVALID_PARAMETER87提供给该方法的参数无效。
ERROR_ALREADY_EXISTS183文件或目录已存在。

可以使用 catch 语句中的 When 子句来处理这些问题,如以下示例所示。

⭐ 示例
using System;
using System.IO;
using System.Text;

class Program
{
    static void Main()
    {
        var sw = OpenStream(@".\textfile.txt");
        if (sw is null)
            return;
        sw.WriteLine("This is the first line.");
        sw.WriteLine("This is the second line.");
        sw.Close();
    }

   static StreamWriter OpenStream(string path)
   {
        if (path is null) {
            Console.WriteLine("You did not supply a file path.");
            return null;
        }

        try {
            var fs = new FileStream(path, FileMode.CreateNew);
            return new StreamWriter(fs);
        }
        catch (FileNotFoundException) {
            Console.WriteLine("The file or directory cannot be found.");
        }
        catch (DirectoryNotFoundException) {
            Console.WriteLine("The file or directory cannot be found.");
        }
        catch (DriveNotFoundException) {
            Console.WriteLine("The drive specified in 'path' is invalid.");
        }
        catch (PathTooLongException) {
            Console.WriteLine("'path' exceeds the maxium supported path length.");
        }
        catch (UnauthorizedAccessException) {
            Console.WriteLine("You do not have permission to create this file.");
        }
        catch (IOException e) when ((e.HResult & 0x0000FFFF) == 32 ) {
            Console.WriteLine("There is a sharing violation.");
        }
        catch (IOException e) when ((e.HResult & 0x0000FFFF) == 80) {
            Console.WriteLine("The file already exists.");
        }
        catch (IOException e) {
            Console.WriteLine($"An exception occurred:\nError code: " +
                              $"{e.HResult & 0x0000FFFF}\nMessage: {e.Message}");
        }
        return null;
    }
}

⭐写在结尾:

文章中出现的任何错误请大家批评指出,一定及时修改。

希望写在这里的小伙伴能给个三连支持

相关文章:

  • LeetCode力扣刷题——千奇百怪的排序算法
  • django基于python的疫情防控下医院人员调动系统--python-计算机毕业设计
  • 详解字符串比较函数:strcmp函数及其模拟实现
  • 【Linux】安装Tomcat以yum方式安装
  • 【羊了个羊】背后的计算机网络原理
  • 面试题--框架篇
  • 字节4面通过,我可以跟面试官要30K吗?
  • Python算法性能分析-时间复杂度
  • java 基本微信小程序的心理咨询服务系统 uniapp 小程序
  • JQ----事件
  • FPGA 20个例程篇:16.HDMI显示彩色风景图
  • 【云原生之Docker实战】使用Docker部署NPS内网穿透工具
  • 应对过载- go-zero源码阅读
  • Python 的“self“参数是什么?
  • 模拟前端ADC芯片LH001-91,用于开发心电、脑电医疗设备
  • (ckeditor+ckfinder用法)Jquery,js获取ckeditor值
  • [NodeJS] 关于Buffer
  • 【划重点】MySQL技术内幕:InnoDB存储引擎
  • 345-反转字符串中的元音字母
  • ES6简单总结(搭配简单的讲解和小案例)
  • java架构面试锦集:开源框架+并发+数据结构+大企必备面试题
  • JS基础篇--通过JS生成由字母与数字组合的随机字符串
  • Shell编程
  • spring boot 整合mybatis 无法输出sql的问题
  • Spring声明式事务管理之一:五大属性分析
  • Sublime text 3 3103 注册码
  • 对话 CTO〡听神策数据 CTO 曹犟描绘数据分析行业的无限可能
  • 服务器之间,相同帐号,实现免密钥登录
  • 每天10道Java面试题,跟我走,offer有!
  • 爬虫进阶 -- 神级程序员:让你的爬虫就像人类的用户行为!
  • 排序(1):冒泡排序
  • 实现简单的正则表达式引擎
  • 腾讯视频格式如何转换成mp4 将下载的qlv文件转换成mp4的方法
  • 因为阿里,他们成了“杭漂”
  • 用jquery写贪吃蛇
  • 优秀架构师必须掌握的架构思维
  • 鱼骨图 - 如何绘制?
  • 字符串匹配基础上
  • PostgreSQL 快速给指定表每个字段创建索引 - 1
  • ​DB-Engines 11月数据库排名:PostgreSQL坐稳同期涨幅榜冠军宝座
  • $.ajax,axios,fetch三种ajax请求的区别
  • (echarts)echarts使用时重新加载数据之前的数据存留在图上的问题
  • (Mirage系列之二)VMware Horizon Mirage的经典用户用例及真实案例分析
  • (Spark3.2.0)Spark SQL 初探: 使用大数据分析2000万KF数据
  • (附源码)springboot 校园学生兼职系统 毕业设计 742122
  • (附源码)springboot人体健康检测微信小程序 毕业设计 012142
  • (附源码)ssm捐赠救助系统 毕业设计 060945
  • (附源码)基于ssm的模具配件账单管理系统 毕业设计 081848
  • (接口封装)
  • (转)菜鸟学数据库(三)——存储过程
  • (转载)VS2010/MFC编程入门之三十四(菜单:VS2010菜单资源详解)
  • .bat批处理(十一):替换字符串中包含百分号%的子串
  • .NET MVC第五章、模型绑定获取表单数据
  • .net 打包工具_pyinstaller打包的exe太大?你需要站在巨人的肩膀上-VC++才是王道
  • .NET 发展历程