#if DEBUG vs. Conditional("DEBUG")

495

在大型项目中使用哪个更好,为什么:

#if DEBUG
    public void SetPrivateValue(int value)
    { ... }
#endif
或者
[System.Diagnostics.Conditional("DEBUG")]
public void SetPrivateValue(int value)
{ ... }

18
请参考此链接:http://blogs.msdn.com/b/ericlippert/archive/2009/09/10/what-s-the-difference-between-conditional-compilation-and-the-conditional-attribute.aspx 以了解关于这个问题的一些想法。 - Eric Lippert
2
如果 (Debugger.IsAttached) {...},你可以使用这个。 - sofsntp
1
Unity开发者注意:DEBUG指的是在编辑器或开发版本中。https://forum.unity.com/threads/solved-is-unity-debug-preprocessor-always-defined-yes-by-design.400542/ - KevinVictor
3
对于所有正在寻找 Eric Lippert 的存档博客文章(不再在 MSDN 上):archive.org 可以帮到你 的人们,这里提供了帮助。 - mbx
4
其实不需要使用archive.org,@EricLippert将他的博客迁移到了这里:(https://ericlippert.com/2009/09/10/whats-the-difference-between-conditional-compilation-and-the-conditional-attribute/) - rold2007
显示剩余2条评论
8个回答

646

这要看你想实现什么:

  • #if DEBUG:在发布时,这里的代码甚至不会到达IL(中间语言)。
  • [Conditional("DEBUG")]:这段代码将抵达IL,但只有在调用方编译时设置了DEBUG才会省略对该方法的调用。

个人而言,我根据情况使用两种方法:

Conditional("DEBUG")示例:我使用这种方式,这样我就不必在发布时回去修改我的代码,但是在调试期间,我想确保我没有犯任何拼写错误。此函数检查我是否正确地键入了属性名称以在我的INotifyPropertyChanged中使用它。

[Conditional("DEBUG")]
[DebuggerStepThrough]
protected void VerifyPropertyName(String propertyName)
{
    if (TypeDescriptor.GetProperties(this)[propertyName] == null)
        Debug.Fail(String.Format("Invalid property name. Type: {0}, Name: {1}",
            GetType(), propertyName));
}

如果你不想在每次调用该函数时都使用相同的 #if DEBUG ,那么你真的不希望使用 #if DEBUG 创建一个函数:

#if DEBUG
    public void DoSomething() { }
#endif

    public void Foo()
    {
#if DEBUG
        DoSomething(); //This works, but looks FUGLY
#endif
    }

对比:

[Conditional("DEBUG")]
public void DoSomething() { }

public void Foo()
{
    DoSomething(); //Code compiles and is cleaner, DoSomething always
                   //exists, however this is only called during DEBUG.
}
#if DEBUG 示例:我在尝试设置不同的 WCF 通信绑定时使用它。
#if DEBUG
        public const String ENDPOINT = "Localhost";
#else
        public const String ENDPOINT = "BasicHttpBinding";
#endif
在第一个示例中,所有代码都存在,但只有在DEBUG打开时才会被忽略。在第二个示例中,根据DEBUG是否设置,常量ENDPOINT将设置为"Localhost"或"BasicHttpBinding"。
更新:我正在更新此答案以澄清一个重要且棘手的问题。如果选择使用ConditionalAttribute,请记住,在编译期间调用会被省略,而不是运行时。也就是说:

MyLibrary.dll

[Conditional("DEBUG")]
public void A()
{
    Console.WriteLine("A");
    B();
}

[Conditional("DEBUG")]
public void B()
{
    Console.WriteLine("B");
}

当库被编译为发布模式时(即没有DEBUG符号),即使调用程序集中定义了DEBUG,从A()内部调用B()的语句也将被省略。


15
#if Debug用于DoSomething不需要让所有的调用语句都被#if DEBUG包围。你可以选择1.只在DoSomething内部使用#if DEBUG,或者是2.在#else下定义一个空白的DoSomething。虽然你的评论帮助我理解了这个区别,但是#if DEBUG并不需要像你展示的那样丑陋。 - Apeiron
4
如果您仅在代码中加入 #if DEBUG 注释,即使在非调试版本中运行代码,JIT 仍可能会包含对该函数的调用。使用“Conditional”特性意味着 JIT 知道在非调试版本中甚至不输出调用位置。 - Jeff Yates
2
@JeffYates:我不明白你写的内容与我所解释的有何不同。 - myermian
1
@Apeiron 如果你只在 #if debug 中有函数内容,那么函数调用仍然会被添加到调用堆栈中。虽然这通常不是很重要,但将声明和函数调用添加到 #if 中意味着编译器的行为就像函数不存在一样,所以 m-y 的方法是使用 #if 更“正确”的方式。尽管两种方法在正常使用中产生的结果无法区分。 - MikeT
7
如果有人想知道,IL代表中间语言 - https://en.wikipedia.org/wiki/Common_Intermediate_Language - jbyrd
显示剩余3条评论

69

值得注意的是,它们并不完全相同。

如果没有定义DEBUG符号,则在第一种情况下,SetPrivateValue本身将不会被调用...... 而在第二种情况下,它将存在,但所有已编译未使用DEBUG符号的调用者都将被省略。

如果代码及其所有调用者都在同一个程序集中,则这种差异就不那么重要了 - 但这意味着在第一种情况下,您还需要在调用代码周围加上#if DEBUG

个人建议采用第二种方法 - 但需要清楚地区分它们之间的区别。


5
需要添加#if语句来调用代码,这意味着会出现大量的#if语句... - Lucas B
虽然第二个选项(条件属性)在某些情况下更好、更清晰,但可能需要通过命名约定等方式来传达在编译过程中方法调用将被从程序集中剥离的事实。 - lysergic-acid

45

我相信很多人会不同意我的观点,但是作为一个构建工程师,经常听到“在我的机器上可以用!”的说法,我认为你几乎永远不应该使用它们。如果你真的需要一些用于测试和调试的东西,那么请想办法将其与实际的生产代码分开来进行测试。

通过单元测试中的模拟来抽象各种情况,针对你想要测试的特定场景制作一次性版本,但不要将调试测试放入二进制代码的测试和生产发布中。这些调试测试只会隐藏潜在的程序错误,直到后续流程中才会被发现。


4
我完全同意你的观点,Jimmy。如果你在测试中使用依赖注入和模拟,为什么还需要在代码中使用#if debug或任何类似的结构呢? - Richard Ev
6
与其仅用于测试,我们经常会在调试版本中使用#if DEBUG将默认收件人电子邮件设置为自己,这样在测试必须传输电子邮件的系统时就不会意外地向其他人发送垃圾邮件。有时这些工具是完成任务的正确选择 :) - iCollect.it Ltd
6
我基本上同意您的观点,但如果您处于性能至关重要的情况下,则不希望用多余的日志记录和用户输出来使代码混乱。但是我完全同意它们不应该被用来改变基本行为。 - MikeT
9
这两种方法都没有问题。声称单元测试和依赖注入可以替代开启调试模式的产品构建,这种想法是幼稚的。 - Ted Bigham
1
我强烈不同意。有时我会将验证包装在 #if DEBUG 中的 throw 语句中,并在 #else 中处理异常情况而不会崩溃(可选地发送异常邮件给我们)。例如,假设一个方法参数不应该为 null,在 #if DEBUG 中如果它是 null 就抛出异常,但在 #else 中分配默认值并向我们发送异常邮件。 - Mickael Bergeron Néron
其中一条评论说,使用#if debug来避免意外发送邮件给人们可能是一个更好的解决方案。这可能是针对这种情况的更好解决方案。https://learn.microsoft.com/zh-cn/dotnet/api/system.net.mail.smtpclient.pickupdirectorylocation?view=net-7.0 - undefined

18

这个也可能很有用:

if (Debugger.IsAttached)
{
...
}

7
就我个人而言,我不认为与其他两种替代方案相比,这种做法有多少用处。这种方法可以保证整个代码块被编译,并且即使在发布版本中也必须在运行时调用 Debugger.IsAttached - Jai

10

对于第一个例子,如果未定义DEBUG,则在构建中将不存在SetPrivateValue,对于第二个例子,如果未定义DEBUG,则调用SetPrivateValue将不会存在于构建中。

在第一个例子中,你需要使用#if DEBUG包装任何对SetPrivateValue的调用。

在第二个例子中,调用SetPrivateValue会被省略,但要注意,SetPrivateValue本身仍将被编译。这对于构建库很有用,因此引用您库的应用程序可以在满足条件时仍然使用函数。

如果您想省略调用并保存被调用者空间,您可以结合使用这两种技术:

[System.Diagnostics.Conditional("DEBUG")]
public void SetPrivateValue(int value){
    #if DEBUG
    // method body here
    #endif
}

5
假设你的代码还有一个#else语句,它定义了一个空的存根函数,解决了Jon Skeet提出的一个问题。这两者之间还有一个重要的区别。
假设#if DEBUGConditional函数存在于由主项目可执行文件引用的DLL中。使用#if,条件的评估将根据库的编译设置执行。使用Conditional属性,条件的评估将根据调用者的编译设置执行。

3

我有一个SOAP WebService扩展,使用自定义的[TraceExtension]来记录网络流量。我仅在Debug构建中使用它,并从Release构建中省略。使用#if DEBUG[TraceExtension]属性包装起来,从而将其从Release构建中删除。

#if DEBUG
[TraceExtension]
#endif
[System.Web.Service.Protocols.SoapDocumentMethodAttribute( ... )]
[ more attributes ...]
public DatabaseResponse[] GetDatabaseResponse( ...) 
{
    object[] results = this.Invoke("GetDatabaseResponse",new object[] {
          ... parmeters}};
}

#if DEBUG
[TraceExtension]
#endif
public System.IAsyncResult BeginGetDatabaseResponse(...)

#if DEBUG
[TraceExtension]
#endif
public DatabaseResponse[] EndGetDatabaseResponse(...)

-1
通常情况下,您需要在 Program.cs 中使用它,以便在 Windows 服务中决定是运行调试还是非调试代码。因此,我创建了一个只读字段 IsDebugMode,并在静态构造函数中设置其值,如下所示。
static class Program
{

    #region Private variable
    static readonly bool IsDebugMode = false;
    #endregion Private variable

    #region Constrcutors
    static Program()
    {
 #if DEBUG
        IsDebugMode = true;
 #endif
    }
    #endregion

    #region Main

    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    static void Main(string[] args)
    {

        if (IsDebugMode)
        {
            MyService myService = new MyService(args);
            myService.OnDebug();             
        }
        else
        {
            ServiceBase[] services = new ServiceBase[] { new MyService (args) };
            services.Run(args);
        }
    }

    #endregion Main        
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接