如何检查IOException是否为Not-Enough-Disk-Space-Exception类型?

72

我该如何检查IOException是否为“磁盘空间不足”异常类型?

目前,我会检查消息是否与“磁盘空间不足”之类的内容匹配,但我知道如果操作系统语言不是英语,这种方法将无法正常工作。


1
你的代码为什么关心IOException的原因是什么? - CodesInChaos
3
当某些东西无法正常工作并且需要用户清理一些磁盘空间时,我需要告知用户此原因。我的外部依赖项在文件系统上运作,并从它们那里获得这样的异常情况。 - jotbek
异常信息已经告诉用户原因了。 - CodesInChaos
9
如果您开发英语语言的应用程序,当其在非英语操作系统上运行时,不能显示OS语言(例如德语)的消息。请注意不改变原意并尽可能使翻译通俗易懂。 - jotbek
5个回答

84

您需要检查HResult并针对ERROR_DISK_FULL (0x70)ERROR_HANDLE_DISK_FULL (0x27)进行测试,这些值可以通过使用OR运算符与0x80070000相结合转换为HResults

对于 .Net Framework 4.5 及以上版本,您可以使用Exception.HResult属性:

static bool IsDiskFull(Exception ex)
{
    const int HR_ERROR_HANDLE_DISK_FULL = unchecked((int)0x80070027);
    const int HR_ERROR_DISK_FULL = unchecked((int)0x80070070);

    return ex.HResult == HR_ERROR_HANDLE_DISK_FULL 
        || ex.HResult == HR_ERROR_DISK_FULL;
}

对于旧版本,您可以使用Marshal.GetHRForException来获取HResult,但这具有重大的副作用,不建议使用

static bool IsDiskFull(Exception ex)
{
    const int ERROR_HANDLE_DISK_FULL = 0x27;
    const int ERROR_DISK_FULL = 0x70;

    int win32ErrorCode = Marshal.GetHRForException(ex) & 0xFFFF;
    return win32ErrorCode == ERROR_HANDLE_DISK_FULL || win32ErrorCode == ERROR_DISK_FULL;
}

来自 MSDN 文档:

请注意,GetHRForException 方法会设置当前线程的 IErrorInfo。这可能会对像 ThrowExceptionForHR 这样默认使用当前线程的 IErrorInfo 的方法产生意外结果。

另请参阅 如何确定 System.IO.IOException 的 HResult?


2
看起来比汉斯的解决方案更干净。 - CodesInChaos
3
另一方面,“Marshal.GetHRForException”具有副作用是非常丑陋的。 - CodesInChaos
我认为这是你能找到的最清晰的解决方案。我会再等一会儿,但我觉得这将被标记为答案。 :) 谢谢! - jotbek
3
从 .Net Framework 4.5 开始,异常的 Exception.HResult 属性 getter 不再是受保护的了。我认为这可以替代 GetHRForException 调用,并摆脱提到的副作用。 - mklein
1
我强烈建议你不要使用GetHRForException。它会引起一个非常奇怪的问题,让我们花了几个月的时间才理解:https://stackoverflow.com/a/40242031/5844190 - Alsty
现在有一个更全面的 HRESULT-具体文档页面,位于 https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/705fb797-2175-4a90-b5a3-3918024b10b8。其中包含所有 Win32 代码,转换为 HRESULT(即在高字中使用 7 facility),以及其他 facility 的结果代码。在同一文档部分中,甚至有一页解释了 HRESULT 值的位布局。 - Peter Duniho

21
在.NET 4.5中,HResult属性getter现在是公共的,因此您不再需要使用Marshal.GetHRForException(以及它的副作用)。

http://msdn.microsoft.com/en-us/library/system.exception.hresult(v=vs.110).aspx说明:"从.NET Framework 4.5开始,HResult属性的setter是受保护的,而其getter是公共的。在之前版本的.NET Framework中,getter和setter都是受保护的"

因此,您可以使用Justin的答案,但将Marshal.GetHRForException(ex)替换为ex.HResult


14

最简单的行内解决方案(需要.NET 4.5和C# 6):

try
{
    //...
}
catch (IOException ex) when ((ex.HResult & 0xFFFF) == 0x27 || (ex.HResult & 0xFFFF) == 0x70)
{
    //...
}

13

好的,这个方法有点巧妙,但我们还是试一下。

首先要做的是从异常中获取HResult。由于它是受保护的成员,所以我们需要使用反射来获取该值。这里有一个扩展方法可以完成这个任务:

public static class ExceptionExtensions
{
    public static int HResultPublic(this Exception exception)
    {
        var hResult = exception.GetType().GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).Where(z => z.Name.Equals("HResult")).First();
        return (int)hResult.GetValue(exception, null);
    }
}

现在,在你的 catch 块中,可以获取 HResult

catch (Exception ex)
{
    int hResult = ex.HResultPublic();
}

从这里开始,你需要解释HResult。你需要查看这个链接

我们需要获取存储在值的前16位中的ErrorCode,因此需要进行一些位操作:

int errorCode = (int)(hResult & 0x0000FFFF);

现在,请参考系统错误代码列表,我们找到了:

ERROR_DISK_FULL
112 (0x70)

因此,请使用以下方式进行测试:

switch (errorCode)
{
    case 112:
        // Disk full
}

也许有一些“更高级”的函数可以获取所有这些东西,但至少它能够工作。


1
+1,实际上(自.NET 4.5以来)它不再那么混乱,因为HResult已经被提升为public。剩下的唯一问题可能是在静态成员中缓存GetProperties调用-这将成为一个很好的包装器,以拥有“向后兼容”的包装器(前提是您同时使用PublicNonPublic绑定标志-您确实这样做了:-))。尽管,在异常处理面前,缓存成员的性能收益可能无关紧要。 - Christian.K
1
为了使代码更加清晰,我将 Exception.HResult 强制转换为基于 ushort 的 HResultErrorCodes 枚举类型,这样可以单独应用 0xFFFF 掩码并消除调用代码中的魔法数字或常量。 - jnm2

1

System.IOException有许多派生的异常类型,但是这些派生类型中没有一个听起来像您的异常。您可以查看异常的HResult或Data属性,也许这将详细说明更具体的错误代码。根据MSDN,这两个属性都是该异常类型的一部分。只需确保您正在尝试捕获特定的异常类型,而不仅仅是基本的Exception类型。


要使用HResult,我需要像这里一样使用反射:http://www.dotnetspider.com/forum/101158-Disk-full-C.aspx,也许比使用消息更好,但仍然是一个hack。我不确定在这种情况下可以在公共数据属性中找到什么规范。 - jotbek
真的,我没有意识到它是受保护的。 - AaronHS

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接