#if DEBUG与条件(“ DEBUG”)


432

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

#if DEBUG
    public void SetPrivateValue(int value)
    { ... }
#endif

要么

[System.Diagnostics.Conditional("DEBUG")]
public void SetPrivateValue(int value)
{ ... }

18
有关此问题的一些想法,请参阅blogs.msdn.com/b/ericlippert/archive/2009/09/10/…
埃里克·利珀特

2
您也可以使用它:if(Debugger.IsAttached){...}
sofsntp

给Unity开发人员的提示:DEBUG表示在编辑器或开发版本中。forum.unity.com/threads/...
KevinVictor

对于您所有人都不再在msdn上
吸引

Answers:


578

这实际上取决于您要做什么:

  • #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,将const ENDPOINT设置为“ Localhost”或“ BasicHttpBinding”。


更新:我正在更新此答案以阐明重要且棘手的问题。如果选择使用ConditionalAttribute,请记住,在编译过程中会忽略调用,而在运行时则不会。那是:

MyLibrary.dll

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

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

当针对发布模式(即,没有DEBUG符号)编译该库时,即使其中包含对的调用,也将永远忽略B()from内A()的调用,A()因为在调用程序集中定义了DEBUG。


13
用于DoSomething的#if调试不需要所有调用语句都被#if DEBUG包围。您可以1:仅#if调试DoSomething的内部内容,或者使用空白的DoSomething定义执行#else。您的评论仍然可以帮助我理解两者之间的区别,但是#if DEBUG不必像您演示的那样丑陋。
Apeiron

3
如果仅#if调试内容,则当您的代码在非调试版本中运行时,JIT可能仍包含对该函数的调用。使用Conditional属性意味着在非DEBUG构建中,JIT甚至不知道输出呼叫站点。
杰夫·耶茨

2
@JeffYates:我看不出你在写什么,跟我解释的有什么不同。

1
@Apeiron如果您仅在#if调试中包含函数内容,则函数调用仍会添加到调用堆栈中,尽管这通常不是很重要,请将声明和函数调用添加到#if中意味着编译器的行为与if函数不存在,因此我的方法是使用#if的更“正确”的方法。尽管两种方法产生的结果在正常使用中都无法区分
MikeT 2014年

5
如果任何人的疑惑,IL =中间语言- en.wikipedia.org/wiki/Common_Intermediate_Language
jbyrd

64

好吧,值得注意的是,它们根本不是同一件事。

如果未定义DEBUG符号,则在第一种情况下SetPrivateValue将不会调用其本身...而在第二种情况下,它将本身存在,但编译时未使用DEBUG符号的任何调用程序都将忽略这些调用。

如果代码及其所有来电号码在同一装配这种差异是不太重要的-但它意味着,在第一种情况下,你需要有#if DEBUG各地的调用代码。

就个人而言,我建议使用第二种方法-但您确实需要清楚地了解它们之间的区别。


5
+1用于调用代码也将需要具有#if语句。这意味着#if语句将会泛滥……
卢卡斯B

尽管在某些情况下,第二个选项(条件属性)更好,更干净,但可能需要传达这样的事实,即在编译过程中会从程序集中剥离方法调用(例如,通过命名约定)。
lysergic-acid

45

我敢肯定,很多人会不同意我的观点,但是作为一名构建专家,我经常听到“但是它在我的机器上可以正常工作!”,我认为您几乎不应该使用它们。如果您确实需要测试和调试的东西,请找出一种使可测试性与实际生产代码分离的方法。

在单元测试中通过模拟来抽象场景,为要测试的一个场景创建一个版本的事物,但是不要将要调试的测试放入要为生产发行版测试和编写的二进制代码中。这些调试测试只是隐藏了开发人员可能遇到的错误,因此直到该过程的后期才发现它们。


4
我完全同意你的吉米。如果您使用DI和模拟进行测试,那么为什么需要#if debug代码中的任何类似构造?
理查德Ev 2010年

@RichardEv也许有更好的方法来处理此问题,但是我目前正在使用它来允许自己通过查询字符串扮演不同用户的角色。我不想在生产中使用此功能,但确实希望将其用于调试,因此我可以控制逐步执行的工作流程,而无需创建多个用户并登录两个帐户即可完成流程。虽然这是我实际上第一次使用它。
Tony

4
我们不仅仅用于测试,还经常在调试版本中使用诸如为自己设置默认收件人电子邮件#if DEBUG这样的方式进行操作,以便在测试必须在过程中传输电子邮件的系统时,不会意外向其他人发送垃圾邮件。有时,这些是完成工作的正确工具:)
Gone Coding 2013年

6
我通常会同意您的意见,但是如果您处于性能至上的情况,那么您就不会因为多余的日志记录和用户输出而使代码混乱,但我确实100%同意不要使用它们来更改代码基本行为
MikeT 2014年

5
-1使用任何一种都没有错。声称单元测试和DI以某种方式替换了产品的启用调试的版本是幼稚的。
Ted Bigham '17

15

这也可能有用:

if (Debugger.IsAttached)
{
...
}

1
就个人而言,与其他2种选择相比,我看不出这有什么用。这样可以保证整个块都已编译,并且Debugger.IsAttached即使在发行版中也必须在运行时调用。
杰伊

9

与第一示例中,SetPrivateValue在构建将不存在,如果DEBUG没有定义,与第二示例中,调用SetPrivateValue如果在构建将不存在DEBUG没有被定义。

在第一个示例中,您还必须包装对SetPrivateValuewith的所有调用#if DEBUG

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

如果要忽略呼叫并节省被呼叫者的空间,可以使用以下两种技术的组合:

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

@P Daddy:#if DEBUG环绕Conditional("DEBUG")并不会删除对该函数的调用,而只是从IL一起删除了该函数,因此您仍在调用不存在的函数(编译错误)。

1
如果不希望代码在发行版中存在,则应将方法主体包装在“ #if DEBUG”中,并可能使用“ #else”存根(带有throw或dummy返回值),并使用该属性来建议来电者不打扰电话吗?这似乎是两全其美。
超级猫

@ myermian,@ supercat:是的,你们都是对的。我的错。我将按照超级猫的建议进行编辑。
P爸爸

5

假设您的代码中还有一条#else语句定义了一个空存根函数,以解决Jon Skeet的要点之一。两者之间还有第二个重要区别。

假设#if DEBUGor Conditional函数存在于主项目可执行文件引用的DLL中。使用#if,将对库的编译设置进行条件的评估。使用该Conditional属性,将针对调用程序的编译设置执行条件的评估。


2

我有一个SOAP WebService扩展,可以使用custom记录网络流量[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(...)

0

通常,您需要在Program.cs中使用它,以便在其中决定对非调试代码运行Debug,而多数情况下则在Windows Services中运行。因此,我创建了一个只读字段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        
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.