#if DEBUG vs. warunkowe („DEBUG”)

432

Którego lepiej użyć i dlaczego w dużym projekcie:

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

lub

[System.Diagnostics.Conditional("DEBUG")]
public void SetPrivateValue(int value)
{ ... }
Lucas B.
źródło
18
Zobacz blogs.msdn.com/b/ericlippert/archive/2009/09/10/..., aby uzyskać przemyślenia na temat tego pytania.
Eric Lippert,
2
możesz tego również użyć: if (Debugger.IsAttached) {...}
sofsntp
Uwaga dla programistów Unity: DEBUG oznacza w edytorze lub w kompilacjach programistycznych. forum.unity.com/threads/…
KevinVictor

Odpowiedzi:

578

To naprawdę zależy od tego, do czego zmierzasz:

  • #if DEBUG: Kod tutaj nie dotrze nawet do IL przy wydaniu.
  • [Conditional("DEBUG")]: Ten kod dotrze do IL, jednak wywołania tej metody zostaną pominięte, chyba że DEBUGA zostanie ustawiona podczas kompilowania wywołującego.

Osobiście korzystam z obu w zależności od sytuacji:

Warunkowy („DEBUG”) Przykład: używam tego, aby nie musiałem wracać i edytować kodu później podczas wydania, ale podczas debugowania chcę się upewnić, że nie napisałem żadnych literówek. Ta funkcja sprawdza, czy poprawnie wpisuję nazwę właściwości podczas próby użycia jej w moich elementach 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));
}

Naprawdę nie chcesz tworzyć funkcji, #if DEBUGchyba że chcesz zawrzeć każde wywołanie tej funkcji tym samym #if DEBUG:

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

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

przeciw:

[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 Przykład DEBUG: Używam tego, gdy próbuję skonfigurować różne wiązania dla komunikacji WCF.

#if DEBUG
        public const String ENDPOINT = "Localhost";
#else
        public const String ENDPOINT = "BasicHttpBinding";
#endif

W pierwszym przykładzie cały kod istnieje, ale jest po prostu ignorowany, chyba że DEBUG jest włączony. W drugim przykładzie stała ENDPOINT jest ustawiona na „Localhost” lub „BasicHttpBinding” w zależności od tego, czy ustawiono DEBUG, czy nie.


Aktualizacja: aktualizuję tę odpowiedź, aby wyjaśnić ważny i trudny punkt. Jeśli zdecydujesz się na użycie ConditionalAttribute, pamiętaj, że połączenia są pomijane podczas kompilacji, a nie w czasie wykonywania . To jest:

MyLibrary.dll

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

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

Kiedy biblioteka jest kompilowana w trybie zwolnienia (tj. Bez symbolu DEBUG), na zawsze będzie B()w niej A()pomijane wezwanie do , nawet jeśli wywołanie to A()jest włączone, ponieważ DEBUG jest zdefiniowany w zestawie wywołującym.

mój
źródło
13
#If Debug dla DoSomething nie musi mieć wszystkich instrukcji wywoływania otoczonych przez #if DEBUG. możesz albo 1: po prostu #if DEBUGOWAĆ wnętrze DoSomething, lub #else z pustą definicją DoSomething. Wciąż twój komentarz pomógł mi zrozumieć różnicę, ale #if DEBUG nie musi być tak brzydki, jak wykazałeś.
Apeiron,
3
Jeśli po prostu #JESZ DEBUGUJ zawartość, JIT może nadal zawierać wywołanie funkcji, gdy kod działa w kompilacji innej niż debugowanie. Użycie atrybutu warunkowego oznacza, że ​​JIT wie, że nawet nie wyświetli strony wywoławczej, gdy jest w wersji innej niż DEBUG.
Jeff Yates,
2
@JeffYates: Nie rozumiem, jak to, co piszesz, różni się od tego, co wyjaśniłem.
moje
1
@Apeiron, jeśli masz tylko zawartość funkcji w debugowaniu #if, wówczas wywołanie funkcji jest nadal dodawane do stosu wywołań, podczas gdy zwykle nie jest to bardzo ważne, dodanie deklaracji i wywołanie funkcji do #if oznacza, że ​​kompilator zachowuje się jak jeśli funkcja nie istnieje, więc moja metoda jest bardziej „poprawnym” sposobem użycia #if. chociaż obie metody dają wyniki, których nie można odróżnić podczas normalnego użytkowania
MikeT
5
jeśli ktoś się zastanawia, IL = Intermediate Language - en.wikipedia.org/wiki/Common_Intermediate_Language
jbyrd
64

Warto zauważyć, że wcale nie mają na myśli tego samego.

Jeśli symbol DEBUG nie jest zdefiniowany, to w pierwszym przypadku SetPrivateValuesam nie będzie wywoływany ... podczas gdy w drugim przypadku będzie istniał, ale wszelkie wywołujące, które są skompilowane bez symbolu DEBUG, będą miały pominięte połączenia.

Jeśli kod i wszystkie jego rozmówców są w tym samym montaż ta różnica jest mniej ważne - ale to oznacza, że w pierwszym przypadku również trzeba mieć #if DEBUGokolice wywołanie kodu, jak również.

Osobiście poleciłbym drugie podejście - ale musisz zachować wyraźną różnicę między nimi.

Jon Skeet
źródło
5
+1 za kod wywołujący również musi zawierać instrukcje #if. Co oznacza, że ​​nastąpi rozpowszechnienie oświadczeń #if ...
Lucas B
Podczas gdy druga opcja (atrybut warunkowy) jest ładniejsza i czystsza w niektórych przypadkach, może być konieczne zakomunikowanie faktu, że wywołanie metody zostanie usunięte z zestawu podczas kompilacji (na przykład przez konwencję nazewnictwa).
kwas lizergowy
45

Jestem pewien, że wielu się ze mną nie zgodzi, ale spędzając czas jako budowniczy, ciągle słysząc „Ale to działa na mojej maszynie!”, Uważam, że prawie nigdy nie powinieneś używać. Jeśli naprawdę potrzebujesz czegoś do testowania i debugowania, znajdź sposób na oddzielenie testowalności od rzeczywistego kodu produkcyjnego.

Streszczenie scenariuszy z kpiną w testach jednostkowych, stwórz jednorazowe wersje rzeczy dla jednorazowych scenariuszy, które chcesz przetestować, ale nie umieszczaj testów do debugowania w kodzie plików binarnych, które testujesz i piszesz w wersji produkcyjnej. Te testy debugowania po prostu ukrywają ewentualne błędy przed programistami, więc nie można ich znaleźć do późniejszego etapu.

Jimmy Hoffa
źródło
4
Całkowicie się z tobą zgadzam Jimmy. Jeśli używasz DI i kpiny do swoich testów, dlaczego miałbyś potrzebować #if debugpodobnej konstrukcji w swoim kodzie?
Richard Ev
@RichardEv Może istnieć lepszy sposób, aby sobie z tym poradzić, ale obecnie używam go, aby pozwolić sobie na odgrywanie roli różnych użytkowników za pomocą ciągu zapytania. Nie chcę tego w produkcji, ale chcę to do debugowania, dzięki czemu mogę kontrolować przepływ pracy, który jest krok po kroku, bez konieczności tworzenia wielu użytkowników i logowania się na oba konta, aby przejść przez przepływ. Chociaż po raz pierwszy musiałem go użyć.
Tony
4
Zamiast tylko testować, często robimy takie rzeczy, jak ustawienie dla siebie domyślnego adresu e-mail odbiorcy w kompilacjach do debugowania, #if DEBUGaby uniknąć przypadkowego spamowania innych podczas testowania systemu, który musi przesyłać wiadomości e-mail w ramach tego procesu. Czasami są to odpowiednie narzędzia do pracy :)
Gone Coding
6
Generalnie zgodziłbym się z tobą, ale jeśli jesteś w sytuacji, w której wydajność jest najważniejsza, nie chcesz zaśmiecać kodu zewnętrznym rejestrowaniem i danymi wyjściowymi użytkowników, ale w 100% zgadzam się, że nie należy ich nigdy używać do zmiany podstawowe zachowanie
MikeT
5
-1 Nie ma nic złego w korzystaniu z jednego z nich. Twierdzenie, że testy jednostkowe i DI w jakiś sposób zastępują kompilację produktu z funkcją debugowania, jest naiwne.
Ted Bigham
15

Ten może być również przydatny:

if (Debugger.IsAttached)
{
...
}
sofsntp
źródło
1
Osobiście nie widzę, jak może to być przydatne w porównaniu z pozostałymi 2 alternatywami. Gwarantuje to, że cały blok jest skompilowany i Debugger.IsAttachednależy go wywoływać w czasie wykonywania, nawet w kompilacjach wersji.
Jai
9

W pierwszym przykładzie SetPrivateValuenie będzie istniał w kompilacji, jeśli DEBUGnie jest zdefiniowany, w drugim przykładzie wywołania do SetPrivateValuenie będą istnieć w kompilacji, jeśli DEBUGnie zostaną zdefiniowane.

Z pierwszego przykładu, będziesz musiał zawijać do jakichkolwiek rozmów SetPrivateValuez #if DEBUGtak dobrze.

W drugim przykładzie wywołania do SetPrivateValuezostaną pominięte, ale należy pamiętać, że SetPrivateValuenadal będzie się kompilować. Jest to przydatne, jeśli budujesz bibliotekę, więc aplikacja odwołująca się do twojej biblioteki może nadal korzystać z twojej funkcji (jeśli warunek jest spełniony).

Jeśli chcesz pominąć połączenia i zaoszczędzić miejsce odbiorcy, możesz użyć kombinacji dwóch technik:

[System.Diagnostics.Conditional("DEBUG")]
public void SetPrivateValue(int value){
    #if DEBUG
    // method body here
    #endif
}
P Tatusiu
źródło
@P Tato: owijania #if DEBUGwokół Conditional("DEBUG")nie usunie połączenia do tej funkcji, to po prostu usuwa funkcję z IL alltogether, więc nadal masz połączenia do funkcji, która nie istnieje (błędy kompilacji).
moje
1
Jeśli nie chcemy, aby kod istniał w wydaniu, należy zawinąć treść metody w „#if DEBUG”, być może za pomocą odcinka „#else” (z wartością zwrócenia lub manekina) i użyć atrybutu, aby zasugerować że dzwoniący nie zawracają sobie głowy rozmową? To wydawałoby się najlepsze z obu światów.
supercat
@myermian, @supercat: Tak, oboje macie rację. Mój błąd. Będę edytować zgodnie z sugestią superkata.
P Daddy,
5

Załóżmy, że twój kod zawierał także #elseinstrukcję definiującą zerową funkcję stub, odnoszącą się do jednego z punktów Jona Skeeta. Istnieje druga ważna różnica między nimi.

Załóżmy, że funkcja #if DEBUGlub Conditionalistnieje w bibliotece DLL, do której odwołuje się główny plik wykonywalny projektu. Korzystając z #if, ocena warunkowa zostanie przeprowadzona w odniesieniu do ustawień kompilacji biblioteki. Za pomocą tego Conditionalatrybutu zostanie przeprowadzona ocena warunkowa w odniesieniu do ustawień kompilacji wywołującego.

Kennet Belenky
źródło
2

Mam rozszerzenie SOAP WebService do rejestrowania ruchu sieciowego przy użyciu niestandardowego [TraceExtension]. Używam tego tylko do kompilacji Debugowania i pomijam kompilacje Release . Użyj, #if DEBUGaby owinąć [TraceExtension]atrybut, usuwając go w ten sposób z kompilacji wersji .

#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(...)
Steven J. Hathaway
źródło
0

Zwykle jest to potrzebne w Program.cs, gdzie chcesz zdecydować się na uruchomienie debugowania na kodzie innym niż debugowanie, a także zbyt często w usługach systemu Windows. Stworzyłem więc pole tylko do odczytu IsDebugMode i ustawiłem jego wartość w konstruktorze statycznym, jak pokazano poniżej.

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        
}
Yashwant Shukla
źródło