ExceptionExtensions.cs 4.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. namespace MeloongCore.Extensions;
  2. public static class ExceptionExtensions {
  3. /// <summary>
  4. /// 返回该异常最底层的 <see cref="Exception.InnerException"/>。
  5. /// </summary>
  6. public static Exception RootException(this Exception ex) {
  7. while (ex.InnerException is { } inner) ex = inner;
  8. return ex;
  9. }
  10. /// <summary>
  11. /// 获取 <paramref name="ex"/> 的用户友好描述。
  12. /// 若 <paramref name="showMultilineStacks"/> 为 true,则返回多行的详细描述与堆栈信息;否则不整理堆栈,仅将 <see cref="Exception.Message"/> 汇总到一行。
  13. /// </summary>
  14. public static string GetDisplay(this Exception? ex, bool showMultilineStacks) {
  15. if (ex is null) return "无可用错误信息!";
  16. // 提取堆栈信息
  17. var lines = new List<string>();
  18. bool isInnerException = false;
  19. for (Exception? currentEx = ex; currentEx is not null; currentEx = currentEx.InnerException) {
  20. if (showMultilineStacks) {
  21. lines.Add((isInnerException ? "→ " : "") + currentEx.Message.ReplaceLineEndings("\r\n", true));
  22. if (currentEx.GetType() != typeof(Exception)) lines.Add(" 错误类型:" + currentEx.GetType().FullName);
  23. var stackLines = (currentEx.StackTrace?.ReplaceLineEndings("\r", true).Split('\r') ?? [])
  24. .Select(l => l.BeforeFirst("(") + l.AfterFirst(")"))
  25. .Distinct();
  26. lines.AddRange(stackLines);
  27. } else {
  28. lines.Add(currentEx.Message.ReplaceLineEndings(" ", true));
  29. }
  30. isInnerException = true;
  31. }
  32. // 分析常见错误原因
  33. string? commonReason = null;
  34. var rootException = ex.RootException();
  35. if (rootException is TypeLoadException or BadImageFormatException or MissingMethodException or NotImplementedException or TypeInitializationException)
  36. commonReason = "运行环境存在问题。请尝试重新安装 .NET Framework 4.8 然后再试。若无法安装,请先卸载较新版本的 .NET Framework,然后再尝试安装。";
  37. else if (rootException is UnauthorizedAccessException)
  38. commonReason = "程序权限不足。请尝试右键程序,选择以管理员身份运行,或将文件移动到其他文件夹。";
  39. else if (rootException is OutOfMemoryException)
  40. commonReason = "系统的运行内存不足。请在关闭一部分不需要的程序后再试。";
  41. else if (rootException is System.Net.Sockets.SocketException && lines.Any(l => l.Contains("WSAStartup")))
  42. commonReason = "请尝试卸载中国移动云盘,然后再试。";
  43. else if (ex.IsNetworkRelated())
  44. commonReason = "你的网络环境不佳,请稍后再试,或使用 VPN 改善网络环境。";
  45. // 输出
  46. if (showMultilineStacks) {
  47. if (commonReason is null) {
  48. return lines.Join("\r\n");
  49. } else {
  50. return commonReason + "\r\n\r\n————————————\r\n详细错误信息:\r\n" + lines.Join("\r\n");
  51. }
  52. } else {
  53. lines = lines.Distinct().ToList();
  54. if (commonReason is null) {
  55. lines.Reverse();
  56. return lines.Join(" ← ");
  57. } else {
  58. return commonReason + "详细错误:" + lines.First();
  59. }
  60. }
  61. }
  62. /// <summary>
  63. /// 判断某个 <paramref name="ex"/> 是否为网络连接不良所导致。
  64. /// </summary>
  65. public static bool IsNetworkRelated(this Exception ex) {
  66. // 提取 Message(不能用 GetDetail,因为堆栈的方法参数中就有 timeout 字样)
  67. string detail = "";
  68. var cur = ex;
  69. detail += cur.Message;
  70. while (cur.InnerException is not null) {
  71. cur = cur.InnerException;
  72. detail += cur.Message;
  73. }
  74. // 判断
  75. if (detail.Contains("(403)")) return false;
  76. return new[] {
  77. "(408)", "超时", "timeout", "网络请求失败", "连接尝试失败", "远程主机强迫关闭了",
  78. "远程方已关闭传输流", "未能解析此远程名称", "由于目标计算机积极拒绝", "基础连接已经关闭"
  79. }.Any(k => detail.ContainsIgnoreCase(k));
  80. }
  81. }