我们经常会使用条件编译符 #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. 解决httpclient请求响应压缩文本乱码问题

    最近在调用京东的获取省份接口老是中文乱码,加了utf-8也没有用.最后在httpclient打的日志中有Content-Encoding:gzip信息,最后在请求header里加上: reqHeade ...

  2. 前端要不要学数据结构&算法

    我们都知道前端开发工程师更多偏向 DOM 渲染和 DOM 交互操作,随之 Node 的推广前端工程师也可以完成服务端开发.对于服务端开发而言大家都觉得数据结构和算法是基础,非学不可.所以正在进行 No ...

  3. 【转】Java运行时数据区简介及堆与栈的区别

    理解JVM运行时的数据区是Java编程中的进阶部分.我们在开发中都遇到过一个很头疼的问题就是OutOfMemoryError(内存溢出错误),但是如果我们了解JVM的内部实现和其运行时的数据区的工作机 ...

  4. http协议报头详解

    目录: 1. http协议简介 2. http报头举例 3. http报头详解 4. 几个字段的说明 5. 总结 6. 参考文章 1. http协议简介 HTTP是Hyper Text Transfe ...

  5. ItemsControl的应用

    ItemsControl是集合类控件的基类,如:ListBox.ComboBox.TreeView 所以,我们可以直接应用“ItemsControl”自定义我们“需要的”集合类型控件

  6. 何时使用img标签,何时使用background-image背景图像

    在什么情况下更适合使用HTML IMG标签来显示一个图像,而不是一个CSS有背景图像,反之亦然? 如下场景使用img标签比较合适: 1.如果图像是等内容的一部分或图表或人(真正的人,而不是股票图人), ...

  7. mui 禁止透明背景点击关闭弹窗遮罩

    //禁止关闭遮罩 window.addEventListener('tap', function(e) { e.target.className == 'mui-backdrop mui-active ...

  8. UVA-11478 Halum (差分约束系统)

    题目大意:一张n个节点的有向带边权图,每次操作能任选一个节点v个一个整数d,使以v为终点的边权值都减少d,以v为起点的边权值都增加d,求若干次操作后的最小边权值的非负最大值. 题目分析:用sum[i] ...

  9. How to find per-process I/O statistics on Linux

    以下转自http://www.xaprb.com/blog/2009/08/23/how-to-find-per-process-io-statistics-on-linux/ Newer Linux ...

  10. windows下使用selenium报错selenium.common.exceptions.WebDriverException: Message: 'geckodriver' executable needs to be in PATH

    问题 :执行程序代码报错: WebDriverException:Message:'geckodriver'executable needs to be in Path 或者 selenium.com ...