ExceptionExtensions.cs 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  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. /// </summary>
  13. /// <param name="onlyRelatedStacks">是否显示所有堆栈,而不是仅与当前。</param>
  14. public static string GetDetail(this Exception? ex, bool onlyRelatedStacks = true) {
  15. if (ex is null) return "无可用错误信息!";
  16. return GetSummary(ex, isDetail: true, onlyRelatedStacks);
  17. }
  18. /// <summary>
  19. /// 提取 <paramref name="ex"/> 的简要描述,将信息汇总到一行。
  20. /// </summary>
  21. public static string GetBrief(this Exception? ex) {
  22. if (ex is null) return "无可用错误信息!";
  23. return GetSummary(ex, isDetail: false);
  24. }
  25. /// <summary>
  26. /// 提取 <paramref name="ex"/> 的描述。
  27. /// </summary>
  28. public static string GetSummary(Exception ex, bool isDetail, bool onlyRelatedStacks = true) {
  29. var innerEx = ex.RootException();
  30. var lines = new List<string>();
  31. bool isInner = false;
  32. for (Exception? cur = ex; cur is not null; cur = cur.InnerException) {
  33. if (isDetail) {
  34. lines.Add((isInner ? "→ " : "") + cur.Message.ReplaceLineEndings("\r\n", true));
  35. if (cur.StackTrace is not null) {
  36. foreach (string stack in cur.StackTrace.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries)) {
  37. if (onlyRelatedStacks && !stack.ContainsIgnoreCase("pcl")) continue;
  38. lines.Add(stack.ReplaceLineEndings(""));
  39. }
  40. }
  41. if (cur.GetType().FullName != "System.Exception") {
  42. lines.Add(" 错误类型:" + cur.GetType().FullName);
  43. }
  44. } else {
  45. lines.Add(cur.Message.ReplaceLineEndings(" ", true));
  46. }
  47. isInner = true;
  48. }
  49. string? commonReason = null;
  50. if (innerEx is TypeLoadException or BadImageFormatException or MissingMethodException or NotImplementedException or TypeInitializationException)
  51. commonReason = "运行环境存在问题。请尝试重新安装 .NET Framework 4.8 然后再试。若无法安装,请先卸载较新版本的 .NET Framework,然后再尝试安装。";
  52. else if (innerEx is UnauthorizedAccessException)
  53. commonReason = "权限不足。请尝试右键程序,选择以管理员身份运行,或将文件移动到其他文件夹。";
  54. else if (innerEx is OutOfMemoryException)
  55. commonReason = "系统的运行内存不足。请在关闭一部分不需要的程序后再试。";
  56. else if (innerEx is System.Net.Sockets.SocketException && lines.Any(l => l.Contains("WSAStartup")))
  57. commonReason = "请尝试卸载中国移动云盘,然后再试。";
  58. else if (ex.IsNetworkRelated())
  59. commonReason = "你的网络环境不佳,请稍后再试,或使用 VPN 改善网络环境。";
  60. if (isDetail) {
  61. if (commonReason is null) {
  62. return lines.Join("\r\n");
  63. } else {
  64. return commonReason + "\r\n\r\n————————————\r\n详细错误信息:\r\n" + lines.Join("\r\n");
  65. }
  66. } else {
  67. lines = lines.Distinct().ToList();
  68. if (commonReason is null) {
  69. lines.Reverse();
  70. return lines.Join(" ← ");
  71. } else {
  72. return commonReason + "详细错误:" + lines.First();
  73. }
  74. }
  75. }
  76. /// <summary>
  77. /// 判断某个 <paramref name="ex"/> 是否为网络连接不良所导致。
  78. /// </summary>
  79. public static bool IsNetworkRelated(this Exception ex) {
  80. // 提取 Message(不能用 GetDetail,因为堆栈的方法参数中就有 timeout 字样)
  81. string detail = "";
  82. var cur = ex;
  83. detail += cur.Message;
  84. while (cur.InnerException is not null) {
  85. cur = cur.InnerException;
  86. detail += cur.Message;
  87. }
  88. // 判断
  89. if (detail.Contains("(403)")) return false;
  90. return new[] {
  91. "(408)", "超时", "timeout", "网络请求失败", "连接尝试失败", "远程主机强迫关闭了",
  92. "远程方已关闭传输流", "未能解析此远程名称", "由于目标计算机积极拒绝", "基础连接已经关闭"
  93. }.Any(k => detail.ContainsIgnoreCase(k));
  94. }
  95. }