#if DEBUG vs. Conditionnel ("DEBUG")

432

Lequel est préférable d'utiliser, et pourquoi, sur un grand projet:

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

ou

[System.Diagnostics.Conditional("DEBUG")]
public void SetPrivateValue(int value)
{ ... }
Lucas B
la source
18
Voir blogs.msdn.com/b/ericlippert/archive/2009/09/10/… pour quelques réflexions sur cette question.
Eric Lippert
2
vous pouvez également l'utiliser: if (Debugger.IsAttached) {...}
sofsntp
Remarque pour les développeurs Unity: DEBUG signifie dans l'éditeur ou dans les builds de développement. forum.unity.com/threads/…
KevinVictor

Réponses:

578

Cela dépend vraiment de ce que vous recherchez:

  • #if DEBUG: Le code ici n'atteindra même pas l'IL à sa sortie.
  • [Conditional("DEBUG")]: Ce code atteindra l'IL, cependant les appels à la méthode seront omis à moins que DEBUG ne soit défini lors de la compilation de l'appelant.

Personnellement j'utilise les deux en fonction de la situation:

Conditionnel ("DEBUG") Exemple: je l'utilise pour ne pas avoir à revenir en arrière et éditer mon code plus tard pendant la sortie, mais pendant le débogage, je veux être sûr de ne pas avoir fait de fautes de frappe. Cette fonction vérifie que je tape correctement un nom de propriété lorsque j'essaie de l'utiliser dans mon contenu 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));
}

Vous ne voulez vraiment pas créer une fonction à #if DEBUGmoins que vous ne souhaitiez boucler chaque appel à cette fonction avec la même chose #if DEBUG:

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

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

contre:

[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 exemple DEBUG: je l'utilise lorsque j'essaie de configurer différentes liaisons pour la communication WCF.

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

Dans le premier exemple, le code existe tous, mais est simplement ignoré sauf si DEBUG est activé. Dans le deuxième exemple, la constante ENDPOINT est définie sur "Localhost" ou "BasicHttpBinding" selon que DEBUG est défini ou non.


Mise à jour: je mets à jour cette réponse pour clarifier un point important et délicat. Si vous choisissez d'utiliser le ConditionalAttribute, gardez à l'esprit que les appels sont omis lors de la compilation et non lors de l'exécution . C'est:

MyLibrary.dll

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

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

Lorsque la bibliothèque est compilée en mode de libération (c'est-à-dire sans symbole DEBUG), l'appel à B()de l'intérieur est A()omis pour toujours, même si un appel à A()est inclus car DEBUG est défini dans l'assembly appelant.

mon
la source
13
Le débogage #if pour DoSomething n'a pas besoin d'avoir toutes les instructions d'appel entourées par #if DEBUG. vous pouvez soit 1: juste #si vous DÉBOGUEZ l'intérieur de DoSomething, ou faites un #else avec une définition vide de DoSomething. Votre commentaire m'a quand même aidé à comprendre la différence, mais #if DEBUG n'a pas besoin d'être aussi laid que vous l'avez démontré.
Apeiron
3
Si vous venez de #if DEBUG le contenu, le JIT peut toujours inclure un appel à la fonction lorsque votre code s'exécute dans une version non débogage. L'utilisation de l'attribut conditionnel signifie que le JIT sait qu'il ne sort même pas le site d'appel dans une version non DEBUG.
Jeff Yates
2
@JeffYates: Je ne vois pas en quoi ce que vous écrivez est différent de ce que j'ai expliqué.
mon
1
@Apeiron si vous n'avez que le contenu de la fonction dans le débogage #if, alors l'appel de fonction est toujours ajouté à la pile d'appels, alors que ce n'est généralement pas très important, l'ajout de la déclaration et de l'appel de fonction au #if signifie que le compilateur se comporte comme si la fonction n'existe pas, la méthode de my est la façon la plus "correcte" d'utiliser #if. bien que les deux méthodes produisent des résultats qui ne peuvent pas être distingués l'un de l'autre en utilisation normale
MikeT
5
si quelqu'un se demande, IL = Intermediate Language - en.wikipedia.org/wiki/Common_Intermediate_Language
jbyrd
64

Eh bien, il convient de noter qu'ils ne signifient pas du tout la même chose.

Si le symbole DEBUG n'est pas défini, alors dans le premier cas le SetPrivateValuelui - même ne sera pas appelé ... alors que dans le second cas il existera, mais tous les appelants qui sont compilés sans le symbole DEBUG verront ces appels omis.

Si le code et tous ses appelants sont dans le même assemblage, cette différence est moins importante - mais cela signifie que dans le premier cas, vous devez également avoir #if DEBUGautour du code appelant .

Personnellement, je recommanderais la deuxième approche - mais vous devez garder la différence entre eux claire dans votre tête.

Jon Skeet
la source
5
+1 pour appeler le code devra également avoir des instructions #if. Ce qui signifie qu'il y aura une prolifération de déclarations #if ...
Lucas B
Bien que la deuxième option (attribut conditionnel) soit plus agréable et plus propre dans certains cas, il peut être nécessaire de communiquer le fait qu'un appel de méthode serait supprimé de l'assembly lors de la compilation (par une convention de dénomination, par exemple).
acide lysergique
45

Je suis sûr que beaucoup seront en désaccord avec moi, mais après avoir passé du temps en tant que gars de la construction à entendre constamment "Mais cela fonctionne sur ma machine!", Je pense que vous ne devriez pratiquement jamais utiliser non plus. Si vous avez vraiment besoin de quelque chose pour les tests et le débogage, trouvez un moyen de séparer cette testabilité du code de production réel.

Résumé des scénarios avec des simulations dans les tests unitaires, faites des versions uniques des choses pour les scénarios uniques que vous souhaitez tester, mais ne placez pas de tests de débogage dans le code des binaires que vous testez et écrivez pour la version de production. Ces tests de débogage masquent simplement les éventuels bogues des développeurs afin qu'ils ne soient trouvés que plus tard dans le processus.

Jimmy Hoffa
la source
4
Je suis totalement d'accord avec toi Jimmy. Si vous utilisez DI et la simulation pour vos tests, pourquoi auriez-vous besoin #if debugd'une construction similaire dans votre code?
Richard Ev
@RichardEv Il peut y avoir une meilleure façon de gérer cela, mais je l'utilise actuellement pour me permettre de jouer le rôle de différents utilisateurs via une chaîne de requête. Je ne veux pas cela en production mais je le veux pour le débogage afin que je puisse contrôler le flux de travail qui est traversé sans avoir à créer plusieurs utilisateurs et à vous connecter aux deux comptes pour parcourir le flux. Bien que ce soit la première fois que je dois l'utiliser.
Tony
4
Plutôt que juste pour les tests, nous faisons souvent des choses comme définir un e-mail de destinataire par défaut pour nous-mêmes, dans les versions de débogage, en utilisant de #if DEBUGsorte que nous ne spammions pas accidentellement les autres tout en testant un système qui doit transmettre des e-mails dans le cadre du processus. Parfois, ce sont les bons outils pour le travail :)
Gone Coding
6
Je serais généralement d'accord avec vous, mais si vous êtes dans une situation où les performances sont primordiales, vous ne voulez pas encombrer le code avec une journalisation et une sortie utilisateur superflues, mais je suis à 100% d'accord pour dire qu'elles ne devraient jamais être utilisées pour modifier le comportement fondamental
MikeT
5
-1 Il n'y a rien de mal à utiliser l'un ou l'autre. Revendiquer des tests unitaires et DI remplace en quelque sorte une version de débogage d'un produit est naïf.
Ted Bigham
15

Celui-ci peut également être utile:

if (Debugger.IsAttached)
{
...
}
sofsntp
la source
1
Personnellement, je ne vois pas en quoi cela peut être utile par rapport aux 2 autres alternatives. Cela garantit que le bloc entier est compilé et Debugger.IsAttacheddoit être appelé au moment de l'exécution, même dans les versions.
Jai
9

Avec le premier exemple, SetPrivateValuen'existera pas dans la build si DEBUGn'est pas défini, avec le second exemple, les appels à SetPrivateValuen'existeront pas dans la build si DEBUGn'est pas défini.

Avec le premier exemple, vous devrez également envelopper tous les appels SetPrivateValueavec #if DEBUG.

Avec le deuxième exemple, les appels à SetPrivateValueseront omis, mais sachez que SetPrivateValuelui - même sera toujours compilé. Ceci est utile si vous créez une bibliothèque, donc une application référençant votre bibliothèque peut toujours utiliser votre fonction (si la condition est remplie).

Si vous souhaitez omettre les appels et économiser l'espace de l'appelé, vous pouvez utiliser une combinaison des deux techniques:

[System.Diagnostics.Conditional("DEBUG")]
public void SetPrivateValue(int value){
    #if DEBUG
    // method body here
    #endif
}
P papa
la source
@P Daddy: Wrapping #if DEBUGaround Conditional("DEBUG")ne supprime pas les appels à cette fonction, il supprime simplement la fonction de IL tous ensemble, donc vous avez toujours des appels à une fonction qui n'existe pas (erreurs de compilation).
mon
1
Si l'on ne veut pas que le code existe dans la version, doit-on envelopper le corps de la méthode dans "#if DEBUG", éventuellement avec un stub "#else" (avec une valeur de retour de jet ou factice), et utiliser l'attribut pour suggérer que les appelants ne se soucient pas de l'appel? Cela semblerait le meilleur des deux mondes.
supercat
@myermian, @supercat: Oui, vous avez tous les deux raison. Mon erreur. Je vais modifier selon la suggestion de supercat.
P Daddy
5

Supposons que votre code contienne également une #elseinstruction qui définit une fonction de stub nul, abordant l'un des points de Jon Skeet. Il y a une deuxième distinction importante entre les deux.

Supposons que la fonction #if DEBUGou Conditionalexiste dans une DLL référencée par l'exécutable de votre projet principal. À l'aide de #if, l'évaluation du conditionnel sera effectuée en fonction des paramètres de compilation de la bibliothèque. En utilisant l' Conditionalattribut, l'évaluation du conditionnel sera effectuée en ce qui concerne les paramètres de compilation de l'invocateur.

Kennet Belenky
la source
2

J'ai une extension SOAP WebService pour enregistrer le trafic réseau à l'aide d'une personnalisation [TraceExtension]. Je l'utilise uniquement pour les versions de débogage et j'omet des versions de version . Utilisez le #if DEBUGpour envelopper l' [TraceExtension]attribut, le supprimant ainsi des versions 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(...)
Steven J. Hathaway
la source
0

Habituellement, vous en auriez besoin dans Program.cs où vous souhaitez décider d'exécuter le débogage sur du code non-débogage et cela principalement dans les services Windows. J'ai donc créé un champ en lecture seule IsDebugMode et défini sa valeur dans le constructeur statique comme indiqué ci-dessous.

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
la source