我们经常会使用条件编译符 #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 配置的,所以第一次获取完毕后就可以缓存下来以便后续使用。


参考资料

如何在 .NET 库的代码中判断当前程序运行在 Debug 下还是 Release 下的更多相关文章

  1. python代码中判断版本

    在python代码中判断python版本: if sys.version_info < (3, 0): lib.make_flows.argtypes = [c_char_p, c_char_p ...

  2. bat代码中判断 bat是否是以管理员权限运行,以及自动以管理员权限运行CMD BAT

    · bat 代码中判断bat是否是以管理员权限运行,以及自动以管理员权限运行CMD BAT 一.判断bat是否是以管理员权限运行 @echo off net.exe session >NUL & ...

  3. 如何在C或C++代码中嵌入ARM汇编代码

    转载自:http://blog.csdn.net/roland_sun/article/details/42921131 大家知道,用C或者C++等高级语言编写的程序,会被编译器编译成最终的机器指令. ...

  4. java中如何在代码中判断时间是否过了10秒

    long previous = 0L; ... { Calendar c = Calendar.getInstance(); long now = c.getTimeInMillis(); //获取当 ...

  5. iOS 中判断应用程序是否为第一次打开

    第一步:在AppDelegate中当应用启动完成后加入一下代码: - (BOOL)application:(UIApplication *)application didFinishLaunching ...

  6. [C#]中获取当前程序运行路径的方法

    获取当前程序运行路径: ①//获取当前 Thread 的当前应用程序域的基目录,它由程序集冲突解决程序用来探测程序集.string str = System.AppDomain.CurrentDoma ...

  7. GA代码中的细节

    GA-BLX交叉-Gaussion变异 中的代码细节: 我写了一个GA的代码,在2005测试函数上一直不能得到与实验室其他同学类似的数量级的结果.现在参考其他同学的代码,发现至少有如下问题: 1.在交 ...

  8. 6.python在windows下用批处理文件在运行中输入程序名直接运行的方法

    最近由于平时自由时间比较多,在看一本python入门书籍,在里面学习了一种用windows下的批处理文件在电脑运行界面中直接输入程序名称就可运行的方法,现将其详细说明如下: 1.首先编写一个教程上的程 ...

  9. 如何:在 DHTML 代码和客户端应用程序代码之间实现双向通信

    https://msdn.microsoft.com/zh-cn/library/a0746166 可以使用 WebBrowser 控件向 Windows 窗体客户端应用程序添加现有的动态 HTML ...

随机推荐

  1. wireshark抓包分析

    TCP协议首部: 分析第一个包: 源地址:我自己电脑的IP,就不放上来了 Destination: 222.199.191.33 目的地址 TCP:表明是个TCP协议 Length:66 表明包的长度 ...

  2. 这样获取celery的结果 有啥隐患没有啊?

  3. 利用Object.defineProperty实现Vue数据双向绑定

    body部分很简单,一个输入框和一个展示的div <div> <p>你好,<input id='nickName'></p> <div id=&q ...

  4. 数据可视化——matplotlib(2)

    导入相关模块 import matplotlib.pyplot as plt import numpy as np import pandas as pd 图表设置 添加X.Y轴标签以及图标标题 a ...

  5. 互联网公司面试必问的Redis题目

    Redis是一个非常火的非关系型数据库,火到什么程度呢?只要是一个互联网公司都会使用到.Redis相关的问题可以说是面试必问的,下面我从个人当面试官的经验,总结几个必须要掌握的知识点. 介绍:Redi ...

  6. Nginx 出现413 Request Entity Too Large得解决方法

    Nginx 出现413 Request Entity Too Large得解决方法 默认情况下使用nginx反向代理上传超过2MB的文件,会报错413 Request Entity Too Large ...

  7. 搞懂分布式技术2:分布式一致性协议与Paxos,Raft算法

    搞懂分布式技术2:分布式一致性协议与Paxos,Raft算法 2PC 由于BASE理论需要在一致性和可用性方面做出权衡,因此涌现了很多关于一致性的算法和协议.其中比较著名的有二阶提交协议(2 Phas ...

  8. 搭建多master的saltstack环境

    0.16.0版本的发布,带来了minion可以连接多Master的特性. 这种方式称为多master( multi-master )配置, 使环境中的SaltStack冗余.在这种配置下,Salt M ...

  9. Vim代码缩进设置

    前段配置VPS,无奈只能使用Vim编辑Python代码,比较头疼的没法设置自动缩进,所以搜索了相关的配置,特记录如下. 将以下的设置加入到~/etc/vim/.vimrc中: set sw=4 set ...

  10. bzoj2501

    题解: 显然,每当进入一个小的边界,那么我们的ans+1,出去一个大的边界,ans-1 然后,我们将每一个边界排序,时间小的在前,大的在后 每一次进来一个,如果是左边的边界,+1,右边的-1 然后输出 ...