处理 .NET 中的 I/O 错误Handling I/O errors in .NET

除了可能在任何方法调用中引发异常(如系统压力过大导致 OutOfMemoryException 或由于编程器错误导致 NullReferenceException),.NET 文件系统方法还可能引发以下异常:In addition to the exceptions that can be thrown in any method call (such as an OutOfMemoryException when a system is stressed or an NullReferenceException due to programmer error), .NET file system methods can throw the following exceptions:

将错误代码映射到异常Mapping error codes to exceptions

由于文件系统为操作系统资源,.NET Core 和 .NET Framework 中的 I/O 方法将包装对基础操作系统的调用。Because the file system is an operating system resource, I/O methods in both .NET Core and .NET Framework wrap calls to the underlying operating system. 当由操作系统执行的代码出现 I/O 错误时,操作系统将对 .NET I/O 方法返回错误信息。When an I/O error occurs in code executed by the operating system, the operating system returns error information to the .NET I/O method. 然后,该方法会将错误信息(通常采用错误代码形式)转换为 .NET 异常类型。The method then translates the error information, typically in the form of an error code, into a .NET exception type. 大多数情况下,可以通过直接将错误代码转换为其相应异常类型来完成此操作;它不基于方法调用的上下文执行任何特殊的错误映射。In most cases, it does this by directly translating the error code into its corresponding exception type; it does not perform any special mapping of the error based on the context of the method call.

例如,在 Windows 操作系统,返回 ERROR_FILE_NOT_FOUND(或 0x02)错误代码的方法调用会映射到 FileNotFoundExceptionERROR_PATH_NOT_FOUND 错误代码(或 0x03)则映射到 DirectoryNotFoundExceptionFor example, on the Windows operating system, a method call that returns an error code of ERROR_FILE_NOT_FOUND (or 0x02) maps to a FileNotFoundException, and an error code of ERROR_PATH_NOT_FOUND (or 0x03) maps to a DirectoryNotFoundException.

但是,操作系统返回特定错误代码的精确条件通常未记录或记录不当。However, the precise conditions under which the operating system returns particular error codes is often undocumented or poorly documented. 因此,会出现意外异常。As a result, unexpected exceptions can occur. 例如,因为使用的是目录而不是文件,可以预料到向 DirectoryInfo.DirectoryInfo 构造函数提供无效目录路径将引发 DirectoryNotFoundExceptionFor example, because you are working with a directory rather than a file, you would expect that providing an invalid directory path to the DirectoryInfo.DirectoryInfo constructor throws a DirectoryNotFoundException. 但是,它也可能引发 FileNotFoundExceptionHowever, it may also throw a FileNotFoundException.

I/O 操作中的异常处理Exception handling in I/O operations

由于操作系统的这一依赖性,相同异常条件(例如在我们的示例中没有发现错误目录)可能会导致 I/O 方法引发任何一种 I/O 异常。Because of this reliance on the operating system, identical exception conditions (such as the directory not found error in our example) can result in an I/O method throwing any one of the entire class of I/O exceptions. 这意味着,在调用 I/O API 时,代码应准备好处理大多数或者所有这些异常,如下表所示:This means that, when calling I/O APIs, your code should be prepared to handle most or all of these exceptions, as shown in the following table:

异常类型Exception type .NET Core.NET Core .NET Framework.NET Framework
IOException Yes Yes
FileNotFoundException Yes Yes
DirectoryNotFoundException Yes Yes
DriveNotFoundException Yes Yes
PathTooLongException Yes Yes
OperationCanceledException Yes Yes
UnauthorizedAccessException Yes Yes
ArgumentException .NET core 2.0 及早期版本.NET Core 2.0 and earlier Yes
NotSupportedException NoNo Yes
SecurityException NoNo 仅受限的信任Limited trust only

处理 IOExceptionHandling IOException

作为 System.IO 命名空间中异常的基类,当任何错误代码未映射到预定义的异常类型时,也将引发 IOExceptionAs the base class for exceptions in the System.IO namespace, IOException is also thrown for any error code that does not map to a predefined exception type. 这意味着异常可以由任何 I/O 操作引发。This means that it can be thrown by any I/O operation.

重要

因为 IOExceptionSystem.IO 命名空间中其他异常类型的基类,应在处理其他 I/O 相关异常后处理 catch 块。Because IOException is the base class of the other exception types in the System.IO namespace, you should handle in a catch block after you've handled the other I/O-related exceptions.

此外,从 .NET Core 2.1 开始,已删除对路径正确性(例如,为了确保路径中不存在无效字符)的验证检查,且运行时会引发从操作系统错误代码(而非从它自己的验证代码)映射的异常。In addition, starting with .NET Core 2.1, validation checks for path correctness (for example, to ensure that invalid characters are not present in a path) have been removed, and the runtime throws an exception mapped from an operating system error code rather than from its own validation code. 在这种情况下,最有可能引发的异常是 IOException,虽然也可能引发任何其他异常类型。The most likely exception to be thrown in this case is an IOException, although any other exception type could also be thrown.

请注意,在异常处理代码中,应始终最后处理 IOExceptionNote that, in your exception handling code, you should always handle the IOException last. 否则,因为它是所有其他 IO 异常的基类,将不会评估派生类的 catch 块。Otherwise, because it is the base class of all other IO exceptions, the catch blocks of derived classes will not be evaluated.

IOException 情况下,可以从 IOException.HResult 属性获取更多错误信息。In the case of an IOException, you can get additional error information from the IOException.HResult property. 若要将 HResult 值转换为 Win32 错误代码,可以删除 32 位值的前 16 位。To convert the HResult value to a Win32 error code, you strip out the upper 16 bits of the 32-bit value. 下表列出了可能包装在 IOException 中的错误代码。The following table lists error codes that may be wrapped in an IOException.

HResultHResult 返回的常量Constant 说明Description
ERROR_SHARING_VIOLATIONERROR_SHARING_VIOLATION 3232 缺少文件名称,或文件或目录正在使用中。The file name is missing, or the file or directory is in use.
ERROR_FILE_EXISTSERROR_FILE_EXISTS 8080 该文件已存在。The file already exists.
ERROR_INVALID_PARAMETERERROR_INVALID_PARAMETER 8787 提供给该方法的参数无效。An argument supplied to the method is invalid.
ERROR_ALREADY_EXISTSERROR_ALREADY_EXISTS 183183 文件或目录已存在。The file or directory already exists.

可以使用 catch 语句中的 When 子句来处理这些问题,如以下示例所示。You can handle these using a When clause in a catch statement, as the following example shows.

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;
    }
}
Imports System.IO

Module Program
    Sub Main(args As String())
        Dim sw = OpenStream(".\textfile.txt")
        If sw Is Nothing Then Return

        sw.WriteLine("This is the first line.")
        sw.WriteLine("This is the second line.")
        sw.Close()
    End Sub

   Function OpenStream(path As String) As StreamWriter
        If path Is Nothing Then
            Console.WriteLine("You did not supply a file path.")
            Return Nothing
        End If

        Try
            Dim fs As New FileStream(path, FileMode.CreateNew)
            Return New StreamWriter(fs)
        Catch e As FileNotFoundException
            Console.WriteLine("The file or directory cannot be found.")
        Catch e As DirectoryNotFoundException
            Console.WriteLine("The file or directory cannot be found.")
        Catch e As DriveNotFoundException
            Console.WriteLine("The drive specified in 'path' is invalid.")
        Catch e As PathTooLongException
            Console.WriteLine("'path' exceeds the maxium supported path length.")
        Catch e As UnauthorizedAccessException
            Console.WriteLine("You do not have permission to create this file.")
        Catch e As IOException When (e.HResult And &h0000FFFF) = 32 
            Console.WriteLine("There is a sharing violation.")
        Catch e As IOException When (e.HResult And &h0000FFFF) = 80
            Console.WriteLine("The file already exists.")
        Catch e As IOException
            Console.WriteLine($"An exception occurred:{vbCrLf}Error code: " +
                              $"{e.HResult And &h0000FFFF}{vbCrLf}Message: {e.Message}")
        End Try
        Return Nothing
    End Function
End Module

请参阅See also