# если DEBUG vs. Conditional («ОТЛАДКА»)

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

Ответы:

578

Это действительно зависит от того, что вы собираетесь:

  • #if DEBUG: Код здесь даже не достигнет IL при выпуске.
  • [Conditional("DEBUG")]: Этот код достигнет IL, однако вызовы метода будут опущены, если только 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 DEBUG внутри DoSomething, либо сделать #else с пустым определением DoSomething. Тем не менее, ваш комментарий помог мне понять разницу, но # если DEBUG не должен быть таким уродливым, как вы продемонстрировали.
Апейрон
3
Если вы просто #if отлаживаете содержимое, JIT может по-прежнему включать вызов функции, когда ваш код выполняется в неотладочной сборке. Использование атрибута «Условие» означает, что JIT не знает даже о том, чтобы вывести сайт вызова в сборке, отличной от DEBUG.
Джефф Йейтс
2
@JeffYates: я не вижу, что то, что вы пишете, отличается от того, что я объяснил.
мой
1
@Apeiron, если у вас есть только содержимое функции в отладке #if, то вызов функции все еще добавляется в стек вызовов, хотя это обычно не очень важно, добавление объявления и вызова функции в #if означает, что компилятор ведет себя как Если функция не существует, метод my является более «правильным» способом использования #if. хотя оба метода дают результаты, которые неотличимы друг от друга при обычном использовании
MikeT
5
если кому-то интересно, IL = Intermediate Language - en.wikipedia.org/wiki/Common_Intermediate_Language
jbyrd
64

Что ж, стоит отметить, что они совсем не имеют в виду одно и то же.

Если символ DEBUG не определен, то в первом случае SetPrivateValueсам не будет называться ... в то время как во втором случае она будет существовать, но любые абоненты , которые компилируются без символа DEBUG будут иметь те вызовы , опущенные.

Если код и все его вызывающие объекты находятся в одной сборке, это различие не так важно, но это означает, что в первом случае вам также необходимо иметь дело #if DEBUGс вызывающим кодом.

Лично я бы порекомендовал второй подход, но вы должны четко понимать разницу между ними.

Джон Скит
источник
5
+1 для вызывающего кода также потребуется оператор #if. Это означает, что произойдет распространение #if заявлений ...
Лукас Б.
Хотя второй вариант (атрибут Conditional) в некоторых случаях более приятный и чистый, он может потребоваться для сообщения о том факте, что вызов метода будет удален из сборки во время компиляции (например, в соответствии с соглашением об именах).
лизергиновая кислота
45

Я уверен, что многие со мной не согласятся, но, проведя время в качестве строителя, постоянно слышавшего: «Но это работает на моей машине!», Я считаю, что вы тоже почти никогда не должны его использовать. Если вам действительно нужно что-то для тестирования и отладки, найдите способ отделить тестируемость от реального производственного кода.

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

Джимми Хоффа
источник
4
Я полностью согласен с тобой, Джимми. Если вы используете DI и макетирование для своих тестов, зачем вам #if debugили подобная конструкция в вашем коде?
Ричард Эв
@RichardEv Возможно, есть лучший способ справиться с этим, но я в настоящее время использую его, чтобы позволить себе играть роль разных пользователей через строку запроса. Я не хочу, чтобы это работало, но я хочу, чтобы оно было отлажено, поэтому я могу контролировать проходящий рабочий процесс, не создавая нескольких пользователей и входя в обе учетные записи, чтобы пройти по потоку. Хотя я впервые использовал его.
Тони
4
Вместо того, чтобы просто тестировать, мы часто делаем такие вещи, как настройка электронной почты получателя по умолчанию для себя, в отладочных сборках, используя, #if DEBUGчтобы мы не случайно спамили других во время тестирования системы, которая должна передавать электронную почту как часть процесса. Иногда это правильные инструменты для работы :)
Gone Coding
6
Я бы в целом согласился с вами, но если вы находитесь в ситуации, когда производительность имеет первостепенное значение, то вы не хотите загромождать код посторонними журналами и пользовательским выводом, но я на 100% согласен, что их никогда не следует использовать для изменения фундаментальное поведение
MikeT
5
-1 Нет ничего плохого в использовании любого из них. Утверждать юнит-тесты и DI каким-то образом заменяет отладочную версию продукта наивно.
Тед
15

Этот также может быть полезен:

if (Debugger.IsAttached)
{
...
}
sofsntp
источник
1
Лично я не вижу, как это может быть полезно по сравнению с двумя другими альтернативами. Это гарантирует, что весь блок скомпилирован и Debugger.IsAttachedдолжен вызываться во время выполнения даже в сборках выпуска.
Jai
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 папа
источник
@P Daddy: Обертывание #if DEBUGвокруг Conditional("DEBUG")не удаляют вызовы этой функции, она только удаляет функцию из IL ALLtogether, так что вы все еще есть вызов функции , которая не существует (ошибки компиляции).
мой
1
Если кто-то не хочет, чтобы код существовал в выпуске, следует обернуть тело метода в «#if DEBUG», возможно, с заглушкой «#else» (с возвращаемым значением throw или фиктивным значением), и использовать атрибут для предложения что звонящие не беспокоятся о звонке? Это казалось бы лучшим из обоих миров.
Суперкат
@myermian, @supercat: Да, вы оба правы. Моя ошибка. Я отредактирую согласно предложению суперкатера.
P Daddy
5

Давайте предположим, что в вашем коде также есть #elseоператор, который определил нулевую функцию-заглушку, адресованную одному из пунктов Джона Скита. Есть второе важное различие между ними.

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

Кеннет Беленький
источник
2

У меня есть расширение SOAP WebService для регистрации сетевого трафика с использованием пользовательских [TraceExtension]. Я использую это только для отладочных сборок и опускаю из сборок выпуска . Используйте атрибут, #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        
}
Яшвант Шукла
источник