#if DEBUG vs. Conditional ("DEBUG")

432

Was ist besser zu verwenden und warum bei einem großen Projekt:

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

oder

[System.Diagnostics.Conditional("DEBUG")]
public void SetPrivateValue(int value)
{ ... }
Lucas B.
quelle
18
Unter blogs.msdn.com/b/ericlippert/archive/2009/09/10/… finden Sie einige Gedanken zu dieser Frage.
Eric Lippert
2
Sie können dies auch verwenden: if (Debugger.IsAttached) {...}
sofsntp
Hinweis für Unity-Entwickler: DEBUG bedeutet im Editor oder in Entwicklungs-Builds. forum.unity.com/threads/…
KevinVictor
Für alle, die nach Eric Lipperts archiviertem Blogpost
verfügbar ist

Antworten:

578

Es hängt wirklich davon ab, was Sie wollen:

  • #if DEBUG: Der Code hier erreicht bei Veröffentlichung nicht einmal die IL.
  • [Conditional("DEBUG")]: Dieser Code erreicht die IL, Aufrufe der Methode werden jedoch weggelassen, es sei denn, DEBUG wird beim Kompilieren des Aufrufers festgelegt.

Persönlich benutze ich beide je nach Situation:

Bedingt ("DEBUG") Beispiel: Ich verwende dies, damit ich später während der Veröffentlichung nicht zurückgehen und meinen Code bearbeiten muss, aber während des Debuggens möchte ich sicher sein, dass ich keine Tippfehler gemacht habe. Diese Funktion überprüft, ob ich einen Eigenschaftsnamen korrekt eingebe, wenn ich versuche, ihn in meinem INotifyPropertyChanged-Material zu verwenden.

[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));
}

Sie möchten wirklich keine Funktion mit erstellen, es #if DEBUGsei denn, Sie sind bereit, jeden Aufruf dieser Funktion mit demselben zu versehen #if DEBUG:

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

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

gegen:

[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-Beispiel: Ich verwende dies, wenn ich versuche, verschiedene Bindungen für die WCF-Kommunikation einzurichten .

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

Im ersten Beispiel ist der gesamte Code vorhanden, wird jedoch nur ignoriert, wenn DEBUG aktiviert ist. Im zweiten Beispiel wird der const ENDPOINT auf "Localhost" oder "BasicHttpBinding" gesetzt, je nachdem, ob DEBUG gesetzt ist oder nicht.


Update: Ich aktualisiere diese Antwort, um einen wichtigen und kniffligen Punkt zu klären. Wenn Sie sich für die Verwendung von entscheiden ConditionalAttribute, beachten Sie, dass Aufrufe während der Kompilierung und nicht zur Laufzeit weggelassen werden . Das ist:

MyLibrary.dll

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

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

Wenn die Bibliothek gegen den Freigabemodus kompiliert wird (dh kein DEBUG-Symbol), wird der Aufruf B()von innen für immer A()weggelassen, selbst wenn ein Aufruf von A()enthalten ist, da DEBUG in der aufrufenden Assembly definiert ist.

meine
quelle
13
Das # if-Debug für DoSomething muss nicht alle aufrufenden Anweisungen enthalten, die von #if DEBUG umgeben sind. Sie können entweder 1: einfach #wenn das Innere von DoSomething DEBUGEN oder #else mit einer leeren Definition von DoSomething ausführen. Ihr Kommentar hat mir trotzdem geholfen, den Unterschied zu verstehen, aber #wenn DEBUG nicht so hässlich sein muss, wie Sie gezeigt haben.
Apeiron
3
Wenn Sie nur #if DEBUG den Inhalt, enthält die JIT möglicherweise immer noch einen Aufruf der Funktion, wenn Ihr Code in einem Nicht-Debug-Build ausgeführt wird. Die Verwendung des Conditional-Attributs bedeutet, dass die JIT nicht einmal die Call-Site ausgibt, wenn sie sich in einem Nicht-DEBUG-Build befindet.
Jeff Yates
2
@ JeffYates: Ich sehe nicht, wie sich das, was du schreibst, von dem unterscheidet, was ich erklärt habe.
Mein
1
@Apeiron Wenn Sie nur den Funktionsinhalt im # if-Debug haben, wird der Funktionsaufruf weiterhin zum Aufrufstapel hinzugefügt, während dies normalerweise nicht sehr wichtig ist. Wenn Sie die Deklaration und den Funktionsaufruf zum #if hinzufügen, verhält sich der Compiler wie folgt Wenn keine Funktion vorhanden ist, ist meine Methode die "korrektere" Art, #if zu verwenden. obwohl beide Methoden Ergebnisse liefern, die im normalen Gebrauch nicht voneinander zu unterscheiden sind
MikeT
5
Wenn sich jemand wundert, IL = Intermediate Language - en.wikipedia.org/wiki/Common_Intermediate_Language
2.
64

Nun, es ist erwähnenswert, dass sie überhaupt nicht dasselbe bedeuten.

Wenn das DEBUG-Symbol nicht definiert ist, wird im ersten Fall das SetPrivateValueselbst nicht aufgerufen ... während es im zweiten Fall existiert, aber bei allen Anrufern , die ohne das DEBUG-Symbol kompiliert wurden, werden diese Aufrufe weggelassen.

Wenn der Code und alle seine Anrufer in der gleichen Anordnung ist dieser Unterschied weniger wichtig - aber es bedeutet , dass im ersten Fall , dass Sie auch brauchen , um #if DEBUGdie um Aufruf als auch Code.

Persönlich würde ich den zweiten Ansatz empfehlen - aber Sie müssen den Unterschied zwischen ihnen in Ihrem Kopf klar halten.

Jon Skeet
quelle
5
+1 für den Aufruf von Code muss ebenfalls # if-Anweisungen enthalten. Was bedeutet, dass es eine Zunahme von # if-Aussagen geben wird ...
Lucas B
Während die zweite Option (bedingtes Attribut) in einigen Fällen besser und sauberer ist, kann es erforderlich sein, die Tatsache zu kommunizieren, dass ein Methodenaufruf während der Kompilierung aus der Assembly entfernt wird (z. B. durch eine Namenskonvention).
Lysergsäure
45

Ich bin mir sicher, dass viele mit mir nicht einverstanden sein werden, aber nachdem ich als Build-Typ ständig gehört habe, dass "Aber es funktioniert auf meiner Maschine!", Habe ich den Standpunkt vertreten, dass Sie es so gut wie nie verwenden sollten. Wenn Sie wirklich etwas zum Testen und Debuggen benötigen, finden Sie heraus, wie Sie diese Testbarkeit vom tatsächlichen Produktionscode trennen können.

Abstrahieren Sie die Szenarien mit Verspottung in Komponententests, erstellen Sie einmalige Versionen von Dingen für einmalige Szenarien, die Sie testen möchten, aber fügen Sie keine Debug-Tests in den Code für Binärdateien ein, die Sie testen und für die Produktionsversion schreiben. Diese Debug-Tests verbergen nur mögliche Fehler vor Entwicklern, sodass sie erst später im Prozess gefunden werden.

Jimmy Hoffa
quelle
4
Ich stimme dir vollkommen zu, Jimmy. Wenn Sie DI verwenden und für Ihre Tests verspotten, warum benötigen Sie dann #if debugein ähnliches Konstrukt in Ihrem Code?
Richard Ev
@RichardEv Es gibt vielleicht eine bessere Möglichkeit, damit umzugehen, aber ich verwende es derzeit, um mir zu erlauben, die Rolle verschiedener Benutzer über eine Abfragezeichenfolge zu spielen. Ich möchte dies nicht in der Produktion, aber ich möchte es zum Debuggen, damit ich den Workflow steuern kann, der durchlaufen wird, ohne mehrere Benutzer erstellen und mich bei beiden Konten anmelden zu müssen, um den Ablauf zu durchlaufen. Obwohl dies das erste Mal ist, dass ich es tatsächlich benutzen musste.
Tony
4
Anstatt nur zu testen, legen wir in Debug-Builds häufig eine Standardempfänger-E-Mail für uns selbst fest, #if DEBUGdamit andere nicht versehentlich Spam erhalten, während wir ein System testen, das E-Mails als Teil des Prozesses übertragen muss. Manchmal sind dies die richtigen Werkzeuge für den Job :)
Gone Coding
6
Ich stimme Ihnen im Allgemeinen zu, aber wenn Sie sich in einer Situation befinden, in der die Leistung von größter Bedeutung ist, möchten Sie den Code nicht mit fremder Protokollierung und Benutzerausgabe überladen, aber ich stimme zu 100% zu, dass sie niemals zum Ändern verwendet werden sollten das grundlegende Verhalten
MikeT
5
-1 Es ist nichts Falsches daran, beides zu verwenden. Es ist naiv zu behaupten, Unit-Tests und DI ersetzen irgendwie einen Debug-fähigen Build eines Produkts.
Ted Bigham
15

Dieser kann auch nützlich sein:

if (Debugger.IsAttached)
{
...
}
sofsntp
quelle
1
Persönlich sehe ich nicht, wie dies im Vergleich zu den anderen beiden Alternativen nützlich sein kann. Dies garantiert, dass der gesamte Block kompiliert wird und Debugger.IsAttachedzur Laufzeit auch in Release-Builds aufgerufen werden muss.
Jai
9

Im ersten Beispiel ist SetPrivateValueder Build nicht vorhanden, wenn er DEBUGnicht definiert ist. Im zweiten Beispiel sind Aufrufe von SetPrivateValuenicht im Build vorhanden, wenn er DEBUGnicht definiert ist.

Im ersten Beispiel müssen Sie auch alle Anrufe SetPrivateValuemit #if DEBUGabschließen.

Im zweiten Beispiel werden die Aufrufe von SetPrivateValueweggelassen, aber beachten Sie, dass SetPrivateValueselbst noch kompiliert wird. Dies ist nützlich, wenn Sie eine Bibliothek erstellen, sodass eine Anwendung, die auf Ihre Bibliothek verweist, Ihre Funktion weiterhin verwenden kann (wenn die Bedingung erfüllt ist).

Wenn Sie die Anrufe weglassen und den Platz des Angerufenen sparen möchten, können Sie eine Kombination der beiden Techniken verwenden:

[System.Diagnostics.Conditional("DEBUG")]
public void SetPrivateValue(int value){
    #if DEBUG
    // method body here
    #endif
}
P Papa
quelle
@P Papa: Wrapping #if DEBUGum Conditional("DEBUG")nicht zu entfernen , die Anrufe zu dieser Funktion, es entfernt nur die Funktion von IL alltogether, so dass Sie immer noch Anrufe Funktion , die nicht (Kompilierungsfehler) nicht vorhanden ist .
Mein
1
Wenn der Code in der Version nicht vorhanden sein soll, sollte der Methodenkörper in "#if DEBUG" eingeschlossen werden, möglicherweise mit einem "#else" -Stub (mit einem Wurf- oder Dummy-Rückgabewert), und das Attribut als Vorschlag verwenden dass sich Anrufer nicht um den Anruf kümmern? Das scheint das Beste aus beiden Welten zu sein.
Supercat
@myermian, @supercat: Ja, ihr habt beide recht. Mein Fehler. Ich werde gemäß dem Vorschlag von Supercat bearbeiten.
P Daddy
5

Nehmen wir an, Ihr Code hatte auch eine #elseAnweisung, die eine Null-Stub-Funktion definierte und einen der Punkte von Jon Skeet ansprach. Es gibt einen zweiten wichtigen Unterschied zwischen den beiden.

Angenommen, die Funktion #if DEBUGoder Conditionalist in einer DLL vorhanden, auf die Ihre ausführbare Hauptprojektdatei verweist. Mit dem #ifwird die Auswertung der Bedingung im Hinblick auf die Kompilierungseinstellungen der Bibliothek durchgeführt. Mit dem ConditionalAttribut wird die Auswertung der Bedingung hinsichtlich der Kompilierungseinstellungen des Aufrufers durchgeführt.

Kennet Belenky
quelle
2

Ich habe eine SOAP WebService-Erweiterung, um den Netzwerkverkehr mithilfe eines benutzerdefinierten Protokolls zu protokollieren [TraceExtension]. Ich verwende dies nur für Debug- Builds und lasse Release- Builds aus. Verwenden Sie das #if DEBUG, um das [TraceExtension]Attribut zu verpacken und es so aus Release- Builds zu entfernen .

#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
quelle
0

Normalerweise benötigen Sie es in Program.cs, wo Sie entscheiden möchten, ob Sie Debuggen mit Nicht-Debug-Code ausführen möchten, und dies auch meistens in Windows-Diensten. Also habe ich ein schreibgeschütztes Feld IsDebugMode erstellt und seinen Wert im statischen Konstruktor wie unten gezeigt festgelegt.

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
quelle