【问题标题】:#if DEBUG vs. Conditional("DEBUG")#if DEBUG vs. Conditional("DEBUG")
【发布时间】:2011-04-16 20:38:13
【问题描述】:

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

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

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

【问题讨论】:

标签: c# debugging preprocessor debug-symbols


【解决方案1】:

嗯,值得注意的是,它们的意思完全不同。

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

如果代码及其所有调用者都在同一个程序集中,则此差异不太很重要-但这意味着在第一种情况下,您需要@987654322在 调用 代码周围也有@。

我个人推荐第二种方法 - 但您确实需要清楚地记住它们之间的区别。

【讨论】:

  • +1 用于调用代码也需要有#if 语句。这意味着#if 语句将会激增......
  • 虽然第二个选项(条件属性)在某些情况下更好更简洁,但可能需要传达一个事实,即在编译期间会从程序集中剥离方法调用(根据命名约定,例如)。
【解决方案2】:

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

对于第一个示例,您还必须使用 #if DEBUG 包装对 SetPrivateValue 的所有调用。

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

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

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

【讨论】:

    【解决方案3】:

    这真的取决于你的目标:

    • #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,否则将被忽略。在第二个示例中,const ENDPOINT 设置为“Localhost”或“BasicHttpBinding”,具体取决于是否设置了 DEBUG。


    更新:我正在更新此答案以澄清一个重要且棘手的问题。如果您选择使用ConditionalAttribute,请记住在编译期间会省略调用,并且不是运行时。那就是:

    MyLibrary.dll

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

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

    【讨论】:

    • DoSomething 的#if 调试不需要将所有调用语句都包含在#if DEBUG 中。您可以 1:仅在 DoSomething 内部进行#if 调试,或者使用 DoSomething 的空白定义执行 #else。您的评论仍然帮助我理解了差异,但#if DEBUG 不必像您展示的那样丑陋。
    • 如果您只是#if DEBUG 内容,当您的代码在非调试版本中运行时,JIT 可能仍会包含对该函数的调用。使用 Conditional 属性意味着 JIT 知道在非调试构建中甚至不输出调用站点。
    • @JeffYates:我看不出你写的和我解释的有什么不同。
    • @Apeiron 如果你只有#if调试中的函数内容,那么函数调用仍然添加到调用堆栈中,虽然这通常不是很重要,添加声明和函数调用到#if 表示编译器的行为就好像函数不存在,所以我的方法是使用#if 的更“正确”的方式。尽管这两种方法产生的结果在正常使用中彼此无法区分
    • 如果有人想知道,IL = 中间语言 - en.wikipedia.org/wiki/Common_Intermediate_Language
    【解决方案4】:

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

    假设#if DEBUGConditional 函数存在于您的主项目可执行文件所引用的DLL 中。使用#if,将根据库的编译设置执行条件评估。使用Conditional 属性,将根据调用程序的编译设置执行条件评估。

    【讨论】:

      【解决方案5】:

      我相信很多人会不同意我的观点,但是作为一名构建人员,我经常听到“但它可以在我的机器上运行!”,我认为您几乎不应该使用任何一种。如果你真的需要一些东西来测试和调试,想办法让可测试性与实际的生产代码分开。

      在单元测试中通过模拟来抽象场景,为您想要测试的一次性场景制作一个单独的版本,但不要将用于调试的测试放入您为生产版本测试和编写的二进制文件的代码中。这些调试测试只是对开发人员隐藏了可能的错误,因此直到过程后期才被发现。

      【讨论】:

      • 我完全同意吉米的观点。如果您在测试中使用 DI 和模拟,为什么您的代码中需要 #if debug 或任何类似的构造?
      • 我们经常做一些事情,比如为自己设置默认收件人电子邮件,在调试版本中,使用#if DEBUG,而不仅仅是为了测试,这样我们在测试必须传输的系统时不会意外地向其他人发送垃圾邮件电子邮件作为该过程的一部分。有时这些是工作的正确工具:)
      • 我通常会同意你的观点,但如果你处于性能最重要的情况,那么你不想用无关的日志记录和用户输出来混乱代码,但我 100% 同意他们永远不应该用来改变基本行为
      • -1 使用其中任何一个都没有问题。声称单元测试和 DI 以某种方式取代了启用调试的产品构建是幼稚的。
      • 我强烈反对。我有时会做并且一直觉得有用的事情是在#if DEBUG 中使用 throw 来包装验证,并在#else 中不崩溃的情况下处理这种情况(可选地向我们发送异常电子邮件)。例如,假设方法参数不应该为 null,然后在 #if DEBUG 中将其为 null 时抛出,但在 #else 中分配默认值并向我们发送异常电子邮件。
      【解决方案6】:

      我有一个 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(...)
      

      【讨论】:

        【解决方案7】:

        这个也很有用:

        if (Debugger.IsAttached)
        {
        ...
        }
        

        【讨论】:

        • 就我个人而言,与其他 2 个替代方案相比,我看不出这有什么用处。这保证了整个块被编译,Debugger.IsAttached 必须在运行时调用,即使在发布版本中也是如此。
        【解决方案8】:

        通常你会在 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        
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-09-13
          • 1970-01-01
          • 2023-03-17
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多