ExceptionExtensions.cs 5.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. using System.Net.Sockets;
  2. namespace MeloongCore.Extensions;
  3. public static class ExceptionExtensions {
  4. /// <summary>
  5. /// 返回该异常最底层的 <see cref="Exception.InnerException"/>。
  6. /// </summary>
  7. public static Exception RootException(this Exception ex) {
  8. while (ex.InnerException is { } inner) ex = inner;
  9. return ex;
  10. }
  11. /// <summary>
  12. /// 获取 <paramref name="ex"/> 的用户友好描述。
  13. /// 若 <paramref name="showMultilineStacks"/> 为 true,则返回多行的详细描述与堆栈信息;否则不整理堆栈,仅将 <see cref="Exception.Message"/> 汇总到一行。
  14. /// </summary>
  15. public static string GetDisplay(this Exception? ex, bool showMultilineStacks) {
  16. if (ex is null) return "无可用错误信息!";
  17. // 提取堆栈信息
  18. var lines = new List<string>();
  19. bool isInnerException = false;
  20. for (Exception? currentEx = ex; currentEx is not null; currentEx = currentEx.InnerException) {
  21. if (showMultilineStacks) {
  22. lines.Add((isInnerException ? "→ " : "") + currentEx.Message.ReplaceLineEndings("\r\n", true));
  23. if (currentEx.GetType() != typeof(Exception)) lines.Add(" 错误类型:" + currentEx.GetType().FullName);
  24. var stackLines = (currentEx.StackTrace?.ReplaceLineEndings("\r", true).Split('\r') ?? [])
  25. .Select(l => l.BeforeFirst("(") + l.AfterFirst(")"))
  26. .Distinct();
  27. lines.AddRange(stackLines);
  28. } else {
  29. lines.Add(currentEx.Message.ReplaceLineEndings(" ", true));
  30. }
  31. isInnerException = true;
  32. }
  33. // 分析常见错误原因
  34. string? commonReason = null;
  35. var rootException = ex.RootException();
  36. if (rootException is TypeLoadException or BadImageFormatException or MissingMethodException or NotImplementedException or TypeInitializationException)
  37. commonReason = "运行环境存在问题。请尝试重新安装 .NET Framework 4.8 然后再试。若无法安装,请先卸载较新版本的 .NET Framework,然后再尝试安装。";
  38. else if (rootException is UnauthorizedAccessException)
  39. commonReason = "程序权限不足。请尝试右键程序,选择以管理员身份运行,或将文件移动到其他文件夹。";
  40. else if (rootException is OutOfMemoryException)
  41. commonReason = "系统的运行内存不足。请在关闭一部分不需要的程序后再试。";
  42. else if (rootException is System.Net.Sockets.SocketException && lines.Any(l => l.Contains("WSAStartup")))
  43. commonReason = "请尝试卸载中国移动云盘,然后再试。";
  44. else if (ex.IsBadNetwork())
  45. commonReason = "你的网络环境不佳,请稍后再试,或使用 VPN 改善网络环境。";
  46. // 输出
  47. if (showMultilineStacks) {
  48. if (commonReason is null) {
  49. return lines.Join("\r\n");
  50. } else {
  51. return commonReason + "\r\n\r\n——————— 详细错误信息 ———————\r\n" + lines.Join("\r\n");
  52. }
  53. } else {
  54. lines = lines.Distinct().ToList();
  55. if (commonReason is null) {
  56. lines.Reverse();
  57. return lines.Join(" ← ");
  58. } else {
  59. return commonReason + "详细错误:" + lines.First();
  60. }
  61. }
  62. }
  63. /// <summary>
  64. /// 判断该异常是否是由于网络连接不良导致。
  65. /// </summary>
  66. public static bool IsBadNetwork(this Exception ex) {
  67. for (Exception? currentEx = ex; currentEx is not null; currentEx = currentEx.InnerException) {
  68. if (currentEx is TimeoutException) {
  69. return true;
  70. } else if (currentEx is HttpRequestCodeException reqEx) {
  71. if (reqEx.StatusCode is HttpStatusCode.Forbidden) return false;
  72. if (reqEx.StatusCode is HttpStatusCode.RequestTimeout) return true;
  73. } else if (currentEx is WebException webEx) {
  74. if (webEx.Response is HttpWebResponse response) {
  75. if (response.StatusCode is HttpStatusCode.Forbidden) return false;
  76. if (response.StatusCode is HttpStatusCode.RequestTimeout) return true;
  77. }
  78. if (webEx.Status is WebExceptionStatus.NameResolutionFailure or WebExceptionStatus.ConnectFailure or WebExceptionStatus.ReceiveFailure or
  79. WebExceptionStatus.SendFailure or WebExceptionStatus.ConnectionClosed or WebExceptionStatus.KeepAliveFailure or WebExceptionStatus.Timeout or
  80. WebExceptionStatus.ProxyNameResolutionFailure) return true;
  81. } else if (currentEx is SocketException socketEx) {
  82. if (socketEx.SocketErrorCode is SocketError.NetworkDown or SocketError.NetworkUnreachable or SocketError.NetworkReset or SocketError.ConnectionAborted or
  83. SocketError.ConnectionReset or SocketError.TimedOut or SocketError.ConnectionRefused or SocketError.HostDown or SocketError.HostUnreachable or
  84. SocketError.HostNotFound or SocketError.TryAgain or SocketError.NoData) return true;
  85. }
  86. }
  87. return false;
  88. }
  89. }