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

如何在 .NET 库的代码中判断当前程序运行在 Debug 下还是 Release 下

我们经常会使用条件编译符 #if DEBUG 在 Debug 下执行某些特殊代码。但是一旦我们把代码打包成 dll,然后发布给其他小伙伴使用的时候,这样的判断就失效了,因为发布的库是 Release 配置的;那些 #if DEBUG 的代码根本都不会编译进库中。然而总有时候希望在库中也能得知程序是 Debug 还是 Release,以便库发布之后也能在 Debug 下多做一些检查。

那么有办法得知使用此库的程序是 Debug 配置还是 Release 配置下编译的呢?本文将介绍一个比较靠谱的方法(适用于 .NET Standard)。


先上代码

using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection;

namespace Walterlv.ComponentModel
{
    /// <summary>
    /// 包含在运行时判断编译器编译配置中调试信息相关的属性。
    /// </summary>
    public static class DebuggingProperties
    {
        /// <summary>
        /// 检查当前正在运行的主程序是否是在 Debug 配置下编译生成的。
        /// </summary>
        public static bool IsDebug
        {
            get
            {
                if (_isDebugMode == null)
                {
                    var assembly = Assembly.GetEntryAssembly();
                    if (assembly == null)
                    {
                        // 由于调用 GetFrames 的 StackTrace 实例没有跳过任何帧,所以 GetFrames() 一定不为 null。
                        assembly = new StackTrace().GetFrames().Last().GetMethod().Module.Assembly;
                    }

                    var debuggableAttribute = assembly.GetCustomAttribute<DebuggableAttribute>();
                    _isDebugMode = debuggableAttribute.DebuggingFlags
                        .HasFlag(DebuggableAttribute.DebuggingModes.EnableEditAndContinue);
                }

                return _isDebugMode.Value;
            }
        }

        private static bool? _isDebugMode;
    }
}

再解释原理

发现特性

所有 .NET 开发者都应该知道我们编译程序时有 Debug 配置和 Release 配置,具体来说是项目文件中一个名为 <Configuration> 的节点记录的字符串。

使用 Debug 编译后的程序和 Release 相比有哪些可以检测到的不同呢?我反编译了我的一个程序集。

.NET Core 程序集,Debug 编译:

[assembly: AssemblyVersion("1.0.0.0")]
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyCompany("Walterlv.Demo")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("Walterlv.Demo")]
[assembly: AssemblyTitle("Walterlv.Demo")]

.NET Core 程序集,Release 编译:

[assembly: AssemblyVersion("1.0.0.0")]
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyCompany("Walterlv.Demo")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("Walterlv.Demo")]
[assembly: AssemblyTitle("Walterlv.Demo")]

发现一个很棒的特性 AssemblyConfiguration,直接写明了当前是 Debug 还是 Release 编译的。

你以为这就完成了?我们再来看看 .NET Framework 下面的情况。

.NET Framework 程序集,Debug 编译:

[assembly: AssemblyVersion("1.0.0.0")]
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("Walterlv.Demo")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Walterlv.Demo")]
[assembly: AssemblyCopyright("Copyright © walterlv 2018")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7", FrameworkDisplayName = ".NET Framework 4.7")]

.NET Framework 程序集,Release 编译:

[assembly: AssemblyVersion("1.0.0.0")]
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Walterlv.Demo")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Walterlv.Demo")]
[assembly: AssemblyCopyright("Copyright © walterlv 2018")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7", FrameworkDisplayName = ".NET Framework 4.7")]

已经没有 AssemblyConfiguration 特性可以用了。不过我们额外发现一个比较间接的特性可用 Debuggable,至少两者都是有的,可以写出兼容的代码。

DebuggableAttribute.DebuggingModes 有多个值:

  • None
    • 自 .NET Framework 2.0 开始,JIT 跟踪信息始终会生成,所以这个属性已经没用了。如果指定为这个值,会直接按 Default 处理。
  • Default
    • 允许 JIT 编译器进行优化。
  • DisableOptimizations
    • 禁止编译器对输出程序集进行优化,因为优化可能导致调试过程非常困难。
  • IgnoreSymbolStoreSequencePoints
  • EnableEditAndContinue
    • 允许在进入断点的情况下编辑代码并继续执行。

通常在 Debug 下编译时,使用的值是 EnableEditAndContinue

寻找程序集

以上发现的程序集特性是需要找到一个程序集的,那么应该使用哪一个程序集呢?

通常我们调试的时候是运行一个入口程序的,所以可以考虑使用 Assembly.GetEntryAssembly() 来获取入口程序集。然而微软官网对此方法有一个描述:

The assembly that is the process executable in the default application domain, or the first executable that was executed by AppDomain.ExecuteAssembly. Can return null when called from unmanaged code.

也就是说如果入口程序集是非托管程序集,那么这个可能返回 null。这可能发生在单元测试中、性能测试中或者其他非托管程序调用托管代码的情况;虽然不是主要场景,却很常见。所以,我们依然需要处理返回 null 的情况。

那么如何才能找到我们需要的入口程序集呢?考虑托管代码的调用栈中的第一个函数可能是最接近使用者调试的程序集的,所以我们可以采取查找栈底的方式:

var assembly = new StackTrace().GetFrames().Last().GetMethod().Module.Assembly;

StackTrace.GetFrames() 方法可能返回 null,但那仅对于一个任意的 StackTrace。在我们的使用场景中是取整个托管调用栈的,由于这个方法本身就是托管代码,所以栈中至少存在一个帧;也就是说此方法在我们的场景中是不可能返回 null 的。所以代码静态检查工具如果提示需要处理 null,其实是多余的担心。

性能

另外,一个编译好的程序集是不可能在运行时再去修改 Debug 和 Release 配置的,所以第一次获取完毕后就可以缓存下来以便后续使用。


参考资料

  • Assembly.GetEntryAssembly Method (System.Reflection)
  • c# - I need an alternative to Assembly.GetEntryAssembly() that never returns null - Stack Overflow
  • StackTrace.GetFrames

相关文章:

  • 在制作跨平台的 NuGet 工具包时,如何将工具(exe/dll)的所有依赖一并放入包中
  • WPF 使用 WindowChrome,在自定义窗口标题栏的同时最大程度保留原生窗口样式(类似 UWP/Chrome)
  • 理解 Roslyn 中的红绿树(Red-Green Trees)
  • (1/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序
  • (2/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序
  • .NET/C# 使窗口永不获得焦点
  • .NET/C# 使用 SpanT 为字符串处理提升性能
  • WPF 应用完全模拟 UWP 的标题栏按钮
  • 让控制台支持 ANSI 转义序列,输出下划线、修改颜色或其他控制
  • 在 GitHub 公开仓库中隐藏自己的私人邮箱地址
  • Win32 程序在启动时激活前一个启动程序的窗口
  • C#/.NET 读取或修改文件的创建时间和修改时间
  • 通过解读 WPF 触摸源码,分析 WPF 插拔设备触摸失效的问题(问题篇)
  • .NET 中各种混淆(Obfuscation)的含义、原理、实际效果和不同级别的差异(使用 SmartAssembly)
  • .NET 中 GetProcess 相关方法的性能
  • Bytom交易说明(账户管理模式)
  • centos安装java运行环境jdk+tomcat
  • Docker 1.12实践:Docker Service、Stack与分布式应用捆绑包
  • java架构面试锦集:开源框架+并发+数据结构+大企必备面试题
  • LeetCode29.两数相除 JavaScript
  • nodejs实现webservice问题总结
  • node入门
  • VirtualBox 安装过程中出现 Running VMs found 错误的解决过程
  • 表单中readonly的input等标签,禁止光标进入(focus)的几种方式
  • 测试开发系类之接口自动化测试
  • 对JS继承的一点思考
  • 高程读书笔记 第六章 面向对象程序设计
  • 关键词挖掘技术哪家强(一)基于node.js技术开发一个关键字查询工具
  • 聊聊flink的BlobWriter
  • 你真的知道 == 和 equals 的区别吗?
  • 七牛云 DV OV EV SSL 证书上线,限时折扣低至 6.75 折!
  • 前端性能优化--懒加载和预加载
  • 如何解决微信端直接跳WAP端
  • 使用 @font-face
  • 网页视频流m3u8/ts视频下载
  • 主流的CSS水平和垂直居中技术大全
  • 进程与线程(三)——进程/线程间通信
  • #git 撤消对文件的更改
  • #pragam once 和 #ifndef 预编译头
  • (rabbitmq的高级特性)消息可靠性
  • (二)c52学习之旅-简单了解单片机
  • (蓝桥杯每日一题)平方末尾及补充(常用的字符串函数功能)
  • (免费领源码)Java#ssm#MySQL 创意商城03663-计算机毕业设计项目选题推荐
  • (转)全文检索技术学习(三)——Lucene支持中文分词
  • .htaccess配置重写url引擎
  • .NET 4.0中的泛型协变和反变
  • .NET/ASP.NETMVC 大型站点架构设计—迁移Model元数据设置项(自定义元数据提供程序)...
  • .NET6 命令行启动及发布单个Exe文件
  • .NET设计模式(7):创建型模式专题总结(Creational Pattern)
  • @font-face 用字体画图标
  • [ CTF ] WriteUp- 2022年第三届“网鼎杯”网络安全大赛(朱雀组)
  • [8-23]知识梳理:文件系统、Bash基础特性、目录管理、文件管理、文本查看编辑处理...
  • [ABP实战开源项目]---ABP实时服务-通知系统.发布模式
  • [BZOJ2281][SDOI2011]黑白棋(K-Nim博弈)
  • [C#]C# OpenVINO部署yolov8图像分类模型