#if DEBUG vs. Conditional ("DEBUG")


432

Що краще використовувати і чому для великого проекту:

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

або

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


2
Ви також можете використовувати це: якщо (Debugger.IsAttached) {...}
sofsntp

Примітка для розробників Unity: DEBUG означає в редакторі або в розробках. forum.unity.com/threads/…
KevinVictor

Відповіді:


578

Це дійсно залежить від того, що ти збираєшся:

  • #if DEBUG: Код тут навіть не потрапить до IL при випуску.
  • [Conditional("DEBUG")]: Цей код надійде до ІЛ, однак виклики методу будуть опущені, якщо не встановлено DEBUG при компіляції абонента.

Особисто я використовую обидва залежно від ситуації:

Умовний ("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), вона назавжди буде мати виклик B()зсередини A(), навіть якщо виклик до A()нього включений, оскільки DEBUG визначений у виклику.


13
Налагодження #if для DoSomething не потребує, щоб усі заяви про виклики були оточені #if DEBUG. ви можете або 1: просто #if ЗАБЕЗПЕЧИТИ внутрішню частину DoSomething, або зробити #else з порожнім визначенням DoSomething. Проте ваш коментар допоміг мені зрозуміти різницю, але #if ДЕБУГ не повинен бути таким некрасивим, як ви продемонстрували.
Апейрон

3
Якщо ви просто #i ЗАБЕЗПЕЧИТИ вміст, JIT все ще може включати виклик до функції, коли ваш код працює у збірці, що не налагоджує. Використання атрибута Conditional означає, що JIT знає навіть не виводити callites, коли він знаходиться в не-DEBUG-збірці.
Jeff Yates

2
@JeffYates: Я не бачу, як те, що ви пишете, відрізняється від того, що я пояснив.
мій

1
@Apeiron, якщо у вас є лише вміст функції у налагодженні #if, тоді виклик функції все ще додається до стеку викликів, хоча це, як правило, не дуже важливо, додавання декларації та виклику функції до #if означає, що компілятор веде себе як якщо функція не існує, то метод мого - це "правильніший" спосіб використання #if. хоча обидва способи дають результати, які не відрізняються один від одного при звичайному використанні
MikeT

5
якщо хтось цікавиться, IL = Intermediate Language - en.wikipedia.org/wiki/Common_Intermediate_Language
jbyrd

64

Ну, варто зазначити, що вони зовсім не означають одне і те ж.

Якщо символ DEBUG не визначений, то в першому випадку SetPrivateValueсам виклик не буде викликаний ... тоді як у другому випадку він буде існувати, але будь-які абоненти , складені без символу DEBUG, ці дзвінки будуть опущені.

Якщо код і всі його викликаючі знаходяться в одній збірці, ця різниця є менш важливою - але це означає, що в першому випадку вам також потрібно мати #if DEBUGнавколо коду виклику .

Особисто я рекомендую другий підхід - але вам потрібно, щоб різниця між ними була чіткою в голові.


5
+1 для коду виклику також має мати #if заяви. Це означає, що буде розповсюджено #if висловлювань ...
Лукас Б

Хоча другий варіант (умовний атрибут) приємніший і чистіший у деяких випадках, можливо, знадобиться повідомити той факт, що виклик методу буде позбавлений зі складання під час компіляції (наприклад, за умовами іменування).
лізергічно-кислотна

45

Я впевнений, що багато хто не погодиться зі мною, але, проводячи час, як будівельник, постійно чуючи "Але це працює на моїй машині!", Я вважаю точку зору, яку ви майже ніколи не використовуєте. Якщо вам дійсно потрібно щось для тестування та налагодження, знайдіть спосіб зробити цю перевіряність відокремленою від фактичного виробничого коду.

Абстрагуйте сценарії з глузуванням в одиничних тестах, складіть одноразові версії речей за одноразовими сценаріями, які ви хочете перевірити, але не вкладайте тести на налагодження в код для бінарних файлів, які ви протестуєте і записуйте для випуску продукції. Ці тести налагодження просто приховують можливі помилки від розробників, щоб їх не було знайдено пізніше.


4
Я повністю згоден з тобою, Джиммі. Якщо ви використовуєте DI та знущаєтесь для своїх тестів, навіщо вам потрібен #if debugчи який-небудь подібний склад у своєму коді?
Річард Єв

@RichardEv Можливо, є кращий спосіб вирішити це, але в даний час я використовую його, щоб дозволити собі грати роль різних користувачів через рядок запитів. Я не хочу цього у виробництві, але я хочу його для налагодження, щоб я міг контролювати перебіг робочого процесу, який пройшов, не створюючи кількох користувачів та входити в обидва облікові записи, щоб пройти по потоку. Хоча це мені вперше довелося використовувати.
Тоні

4
Замість того, щоб просто тестувати, ми часто робимо такі речі, як встановлення електронної пошти одержувача за замовчуванням для себе, в налагодженнях, використовуючи #if DEBUGтак, щоб ми випадково не спамували інших під час тестування системи, яка повинна передавати електронні листи в рамках процесу. Іноді це правильні інструменти для роботи :)
Пройшло кодування

6
Я, як правило, погоджуюся з вами, але якщо ви перебуваєте в ситуації, коли продуктивність є найважливішою, то ви не хочете захаращувати код сторонніми журналами та вихідними повідомленнями користувачів, але я на 100% погоджуюся, що вони ніколи не повинні використовуватися для зміни основна поведінка
MikeT

5
-1 Немає нічого поганого у використанні будь-якого з цих. Претензійні тестові одиниці та DI якимось чином замінюють побудову продукту з налагодженням наївним.
Тед Бігхем

15

Цей також може бути корисним:

if (Debugger.IsAttached)
{
...
}

1
Особисто я не бачу, як це може бути корисним порівняно з іншими двома альтернативами. Це гарантує, що весь блок зібраний і його Debugger.IsAttachedпотрібно викликати під час виконання навіть у версії версій.
Джай

9

У першому прикладі, SetPrivateValueне існуватиме в збірці , якщо DEBUGне визначене, то з другим , наприклад, вимагає , щоб SetPrivateValueне існуватиме в збірці , якщо DEBUGне визначене.

У першому прикладі, вам доведеться обернути будь-які дзвінки SetPrivateValueз #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" (із зворотним значенням кидка чи манекена) та використати атрибут, щоб запропонувати що абоненти не турбуються з викликом? Це здавалося б найкращим з обох світів.
supercat

@myermian, @supercat: Так, ви обоє праві. Моя помилка. Я відредагую відповідно до пропозиції суперката.
П тато

5

Припустимо, ваш код також мав #elseвислів, який визначав нульову функцію заглушки, звертаючись до одного з пунктів Джона Скіта. Існує друга важлива відмінність між ними.

Припустимо, що функція #if DEBUGабо Conditionalіснує в DLL, на яку посилається ваш головний виконуваний проект. Використання#if , оцінювання умовного буде виконано з урахуванням параметрів компіляції бібліотеки. Використовуючи Conditionalатрибут, оцінювання умовного буде виконано стосовно налаштувань компіляції виклику.


2

У мене є розширення SOAP WebService для реєстрації мережевого трафіку за допомогою користувацького [TraceExtension]. Я використовую це лише для налагодження налагодження та пропуску з версій версій. Використовуйте атрибут, #if DEBUGщоб обернути [TraceExtension]атрибут, видаливши його з версій версій.

#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, де ви хочете вирішити запустити налагодження на коді, що не налагоджує, і в основному в службах 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        
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.