当前位置: 首页 > news >正文

.NET/C# 中设置当发生某个特定异常时进入断点(不借助 Visual Studio 的纯代码实现)

使用 Visual Studio 可以帮助我们在发生异常的时候中断,便于我们调试程序出现异常那一时刻的状态。如果没有 Visual Studio 的帮助(例如运行已发布的程序),当出现某个或某些特定异常的时候如何能够迅速进入中断的环境来调试呢?

本文介绍如何实现在发生特定异常时中断,以便调查此时程序的状态的纯代码实现。


第一次机会异常

.NET 程序代码中的任何一段代码,在刚刚抛出异常,还没有被任何处理的那一时刻,AppDomain 的实例会引发一个 FirstChanceException 事件,用于通知此时刚刚开始发生了一个异常。

于是我们可以通过监听第一次机会异常来获取到异常刚刚发生那一刻而还没有被 catch 的状态:

using System;
using System.IO;
using System.Runtime.ExceptionServices;

namespace Walterlv.Demo.DoubiBlogs
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            AppDomain.CurrentDomain.FirstChanceException += OnFirstChanceException;

            // 这里是程序的其他代码。
        }

        private static void OnFirstChanceException(object sender, FirstChanceExceptionEventArgs e)
        {
            // 在这里,可以通过 e.Exception 来获取到这个异常。
        }
    }
}

在第一次机会异常处中断

我在这篇博客中举了一个例子来说明如何在发生异常的时候中断,不过是使用 Visual Studio:

  • 在 Visual Studio 中设置当发生某个特定异常或所有异常时中断

那么现在我们使用第一次机会异常来完善一下其中的代码:

using System;
using System.IO;
using System.Runtime.ExceptionServices;

namespace Walterlv.Demo.DoubiBlogs
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            AppDomain.CurrentDomain.FirstChanceException += OnFirstChanceException;

            try
            {
                File.ReadAllText(@"C:\walterlv\逗比博客\不存在的文件.txt");
            }
            catch (IOException)
            {
                Console.WriteLine("出现了异常");
            }
        }

        private static void OnFirstChanceException(object sender, FirstChanceExceptionEventArgs e)
        {
            // 现在,我们使用 Debugger.Break() 来中断程序。
            Debugger.Break();
        }
    }
}

保持 Visual Studio 异常设置窗格中的异常设置处于默认状态(意味着被 catch 的异常不会在 Visual Studio 中中断)。

现在运行这个程序,你会发现程序发生了中断,在我们写下了 Debugger.Break() 的那段代码上。

程序发生中断

而在这个时候查看 Visual Studio 中程序的堆栈,可以发现其实调用堆栈是接在一开始发生异常的那一个方法的后面的,而且是除了非托管代码之外帧都是相邻的。

应用程序堆栈

双击 Visual Studio 堆栈中亮色的帧,即可定位到我们自己写的代码。因此,双击第一个亮色的帧可以转到我们自己写的代码中第一个引发异常的代码块。这个时候可以查看应用程序中各处的状态,这正好是发生此熠时的状态(而不是 catch 之后的状态)。

优化代码和提示

为了让这段代码包装得更加“魔性”,我们可以对第一次机会异常的事件加以处理。现在,我们这么写:

[DebuggerStepThrough, DebuggerNonUserCode]
private static void OnFirstChanceException(object sender, FirstChanceExceptionEventArgs e)
    => ExceptionDebugger.Break();

用到的 ExceptionDebugger 类型如下:

using System.Diagnostics;

namespace Walterlv.Demo.DoubiBlogs
{
    internal class ExceptionDebugger
    {
        // 现在请查看 Visual Studio 中的堆栈以迅速定位刚刚发生异常时的程序状态。
        // 如果你按下 F10,可以立刻但不跳转到你第一个出现异常的代码块中。
        private static void BreakCore() => Debugger.Break();




        // 现在请查看 Visual Studio 中的堆栈以迅速定位刚刚发生异常时的程序状态。
        // 如果你按下 F10,可以立刻但不跳转到你第一个出现异常的代码块中。
        private static void LaunchCore() => Debugger.Launch();




        [DebuggerStepThrough, DebuggerNonUserCode]
        internal static void Break()
        {
            if (Debugger.IsAttached)
            {
                BreakCore();
            }
            else
            {
                LaunchCore();
            }
        }
    }
}

现在,发生了第一次机会异常的时候,会断点在我们写的 BreakCore 方法上。这里的代码很少,因此开发者看到这里的时候可以很容易地注意到上面的注释以了解到如何操作。

自己设的断点

现在再看堆栈,依然像前面一样,找到第一个亮色的帧可以找到第一个抛出异常的我们的代码。

调用堆栈

注意,我们在从第一次机会异常到后面中断的代码中,都设置了这两个特性:

  • DebuggerStepThrough 设置此属性可以让断点不会出现在写的这几个方法中
    • 于是,当你按下 F10 的时候,会跳过所有标记了此特性的方法,这可以直接跳转到最终发生异常的那段代码中去。
  • DebuggerNonUserCode 设置此代码非用户编写的代码
    • 于是,在 Visual Studio 的堆栈中,我们会发现这几个方法会变成暗色的,Visual Studio 不会优先显式这部分的源代码,这可以让错误在最关键的代码中显示而不会被我们刚刚写的这些代码中污染。

附加调试器

前面的代码中,我们做了一个判断 Debugger.IsAttached。这是在判断,如果当前没有附加调试器,那么就附加一个。

于是这段代码可以运行在非 Visual Studio 的环境中,当出现了异常的时候,还可以补救选择一个调试器。

附加调试器

当然,实际上附加到 Visual Studio 进行调试也是最佳的方法。只不过,我们不需要一定通过 Visual Studio,我们可以在一般测试代码的时候也能获得出现特定异常时立刻开始断点调查异常的特性。


我的博客会首发于 https://blog.walterlv.com/,而 CSDN 会从其中精选发布,但是一旦发布了就很少更新。

如果在博客看到有任何不懂的内容,欢迎交流。我搭建了 dotnet 职业技术学院 欢迎大家加入。

知识共享许可协议

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名吕毅(包含链接:https://walterlv.blog.csdn.net/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系。

相关文章:

  • 如何在 Windows 10 中安装 WSL2 的 Linux 子系统
  • 如何安装和准备 Visual Studio 扩展/插件开发环境
  • 基于 Roslyn 同时为 Visual Studio 插件和 NuGet 包开发 .NET/C# 源代码分析器 Analyzer 和修改器 CodeFixProvider
  • 软件界面中一些易混淆/易用错的界面文案,以及一些约定俗成的文案约定
  • WPF 的 VisualBrush 只刷新显示的视觉效果,不刷新布局范围
  • .NET/C# 使用 #if 和 Conditional 特性来按条件编译代码的不同原理和适用场景
  • 使用 Roslyn 分析代码注释,给 TODO 类型的注释添加负责人、截止日期和 issue 链接跟踪
  • 为 NuGet 指定检测的 MSBuild 路径或版本,解决 MSBuild auto-detection: using msbuild version 自动查找路径不合适的问题
  • 解决方案文件 sln 中的项目类型 GUID
  • 两种方法设置 .NET/C# 项目的编译顺序,而不影响项目之间的引用
  • 理解 Visual Studio 解决方案文件格式(.sln)
  • nuget.exe 还原解决方案 NuGet 包的时候出现错误:调用的目标发生了异常。Error parsing the nested project section in solution file
  • 找出 .NET Core SDK 是否使用预览版的全局配置文件在哪里(探索篇)
  • 如何在 Visual Studio 2019 中设置使用 .NET Core SDK 的预览版(全局生效)
  • 使用基于 Roslyn 的 Microsoft.CodeAnalysis.PublicApiAnalyzers 来追踪项目的 API 改动,帮助保持库的 API 兼容性
  • [分享]iOS开发 - 实现UITableView Plain SectionView和table不停留一起滑动
  • 【面试系列】之二:关于js原型
  • 2018一半小结一波
  • C语言笔记(第一章:C语言编程)
  • Flannel解读
  • Java到底能干嘛?
  • JAVA之继承和多态
  • js操作时间(持续更新)
  • nodejs调试方法
  • webpack项目中使用grunt监听文件变动自动打包编译
  • 等保2.0 | 几维安全发布等保检测、等保加固专版 加速企业等保合规
  • - 概述 - 《设计模式(极简c++版)》
  • 欢迎参加第二届中国游戏开发者大会
  • 离散点最小(凸)包围边界查找
  • 浏览器缓存机制分析
  • 如何合理的规划jvm性能调优
  • 微信支付JSAPI,实测!终极方案
  • 小程序、APP Store 需要的 SSL 证书是个什么东西?
  • 宾利慕尚创始人典藏版国内首秀,2025年前实现全系车型电动化 | 2019上海车展 ...
  • ​Java并发新构件之Exchanger
  • ​比特币大跌的 2 个原因
  • !!java web学习笔记(一到五)
  • #pragma once与条件编译
  • #QT(一种朴素的计算器实现方法)
  • (1)bark-ml
  • (aiohttp-asyncio-FFmpeg-Docker-SRS)实现异步摄像头转码服务器
  • (Matlab)使用竞争神经网络实现数据聚类
  • (Matlab)遗传算法优化的BP神经网络实现回归预测
  • (二十三)Flask之高频面试点
  • (附源码)springboot 房产中介系统 毕业设计 312341
  • (黑马出品_高级篇_01)SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式
  • (六)激光线扫描-三维重建
  • (论文阅读32/100)Flowing convnets for human pose estimation in videos
  • (小白学Java)Java简介和基本配置
  • (一)基于IDEA的JAVA基础10
  • (原創) 如何使用ISO C++讀寫BMP圖檔? (C/C++) (Image Processing)
  • (转)总结使用Unity 3D优化游戏运行性能的经验
  • .desktop 桌面快捷_Linux桌面环境那么多,这几款优秀的任你选
  • .NET 4.0中的泛型协变和反变
  • .NET Compact Framework 多线程环境下的UI异步刷新