我们经常会使用条件编译符 #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. java -d . **.java 与 java **.java 的区别

    如何在命令行模式下运行带包的java文件 https://blog.csdn.net/lytor/article/details/17048361 javac,使用"-d ."与省 ...

  2. 关于Vue的component制作dialog组件

    其实原理很简单,兴个粟子, 点击按钮出现 dialog 弹出杠, 将dialog做成一个组件,components/dialog.vue 就是在components里面新建一个vue.将这个vue做为 ...

  3. vue下个兄弟节点

    checkOne(e) { e.currentTarget.nextElementSibling.style.background = 'red' }

  4. authentication vs authorization 验证与授权的区别

    认证和授权的区别 Authentication vs. Authorization简单来说,认证(Authentication )是用来回答以下问题: 用户是谁 当前用户是否真的是他所代表的角色 通常 ...

  5. LabVIEW之生产者/消费者模式

    LabVIEW之生产者/消费者设计模式 彭会锋

  6. 《深入理解mybatis原理3》 Mybatis数据源与连接池

    <深入理解mybatis原理> Mybatis数据源与连接池 对于ORM框架而言,数据源的组织是一个非常重要的一部分,这直接影响到框架的性能问题.本文将通过对MyBatis框架的数据源结构 ...

  7. Python之NumPy中线性代数

    参考博客:http://blog.csdn.net/u013930163/article/details/51839983 网站:https://docs.scipy.org/doc/numpy-de ...

  8. mysql 在创建表或者插入时遇到关键字报错

    mysql 在创建表或者插入时遇到关键字:比如name,status等.都不报错 解决方法:在字段上加上` 上面这个符号是键盘ecs下面那个符号

  9. 2016ACM/ICPC亚洲区大连站现场赛题解报告(转)

    http://blog.csdn.net/queuelovestack/article/details/53055418 下午重现了一下大连赛区的比赛,感觉有点神奇,重现时居然改了现场赛的数据范围,原 ...

  10. restframework api(基础1)

    最近项目忙成狗,都没时间好好看看开发的东西了,正好最近开始看rest api的东西,真是个好东西啊.可以前后端分离,但是在学习的过程中,遇到各种问题.还是基础不够扎实.本次rest api的会一边遇坑 ...