#if DEPURACIÓN vs. Condicional ("DEPURACIÓN")

432

Cuál es mejor usar y por qué, en un proyecto grande:

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

o

[System.Diagnostics.Conditional("DEBUG")]
public void SetPrivateValue(int value)
{ ... }
Lucas B
fuente
18
Ver blogs.msdn.com/b/ericlippert/archive/2009/09/10/… para algunas reflexiones sobre esta pregunta.
Eric Lippert
2
también puede usar esto: if (Debugger.IsAttached) {...}
sofsntp
Nota para los desarrolladores de Unity: DEBUG significa en el editor o en las compilaciones de desarrollo. forum.unity.com/threads/…
KevinVictor

Respuestas:

578

Realmente depende de lo que estés buscando:

  • #if DEBUG: El código aquí ni siquiera llegará al IL en el lanzamiento.
  • [Conditional("DEBUG")]: Este código alcanzará el IL, sin embargo , se omitirán las llamadas al método a menos que se defina DEBUG cuando se compila la persona que llama.

Personalmente uso ambos dependiendo de la situación:

Ejemplo condicional ("DEPURACIÓN"): Utilizo esto para no tener que volver atrás y editar mi código más tarde durante el lanzamiento, pero durante la depuración quiero estar seguro de que no hice ningún error tipográfico. Esta función comprueba que escribo un nombre de propiedad correctamente cuando intento usarlo en mi material 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));
}

Realmente no desea crear una función utilizando a #if DEBUGmenos que esté dispuesto a ajustar cada llamada a esa función con el mismo #if DEBUG:

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

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

versus:

[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 ejemplo: lo uso cuando intento configurar diferentes enlaces para la comunicación WCF.

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

En el primer ejemplo, todo el código existe, pero solo se ignora a menos que DEBUG esté activado. En el segundo ejemplo, el const ENDPOINT se establece en "Localhost" o "BasicHttpBinding" dependiendo de si DEBUG está configurado o no.


Actualización: estoy actualizando esta respuesta para aclarar un punto importante y complicado. Si elige usar el ConditionalAttribute, tenga en cuenta que las llamadas se omiten durante la compilación, y no el tiempo de ejecución . Es decir:

MyLibrary.dll

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

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

Cuando la biblioteca se compila contra el modo de liberación (es decir, sin símbolo DEPURACIÓN), siempre se omitirá la llamada B()desde adentro A(), incluso si A()se incluye una llamada a porque DEBUG está definido en el ensamblado de la llamada.

mi
fuente
13
El #if Debug para DoSomething no necesita tener todas las declaraciones de llamada rodeadas por #if DEBUG. puede 1: simplemente #si DEPURAR el interior de DoSomething o hacer un #else con una definición en blanco de DoSomething. Aún así, tu comentario me ayudó a entender la diferencia, pero #if DEBUG no tiene por qué ser tan feo como has demostrado.
Apeiron
3
Si solo #if DEPURA el contenido, el JIT aún puede incluir una llamada a la función cuando su código se ejecuta en una compilación sin depuración. El uso del atributo condicional significa que el JIT sabe que ni siquiera genera el sitio de llamadas cuando está en una compilación que no es DEPURACIÓN.
Jeff Yates
2
@JeffYates: No veo cómo lo que estás escribiendo es diferente de lo que expliqué.
Mi
1
@Apeiron si solo tiene el contenido de la función en la depuración #if, entonces la llamada a la función todavía se agrega a la pila de llamadas, mientras que esto generalmente no es muy importante, agregar la declaración y la llamada a la función #if significa que el compilador se comporta como si la función no existe, entonces mi método es la forma más "correcta" de usar #if. aunque ambos métodos producen resultados que no se pueden distinguir entre sí en el uso normal
MikeT
55
si alguien se pregunta, IL = lenguaje intermedio - en.wikipedia.org/wiki/Common_Intermediate_Language
jbyrd
64

Bueno, vale la pena señalar que no significan lo mismo en absoluto.

Si el símbolo DEBUG no está definido, entonces en el primer caso SetPrivateValueno se llamará a sí mismo ... mientras que en el segundo caso existirá, pero las llamadas que se compilan sin el símbolo DEBUG tendrán esas llamadas omitidas.

Si el código y todas las personas que llaman están en el mismo ensamblado, esta diferencia es menos importante, pero significa que en el primer caso también debe tener #if DEBUGalrededor del código de llamada .

Personalmente, recomendaría el segundo enfoque, pero debe mantener clara la diferencia entre ellos.

Jon Skeet
fuente
55
+1 para el código de llamada también necesitará tener declaraciones #if. Lo que significa que habrá una proliferación de declaraciones #if ...
Lucas B
Si bien la segunda opción (atributo condicional) es más agradable y limpia en algunos casos, puede ser necesario comunicar el hecho de que una llamada al método se eliminaría del ensamblado durante la compilación (por una convención de nomenclatura, por ejemplo).
ácido lisérgico
45

Estoy seguro de que muchas personas estarán en desacuerdo conmigo, pero después de haber pasado tiempo como un tipo de compilación constantemente escuchando "¡Pero funciona en mi máquina!", Considero que tampoco deberías usarlo. Si realmente necesita algo para probar y depurar, encuentre una manera de hacer que esa capacidad de prueba se separe del código de producción real.

Resuma los escenarios con burlas en las pruebas unitarias, haga versiones únicas de las cosas para los escenarios únicos que desea probar, pero no ponga pruebas de depuración en el código de los binarios que prueba y escribe para la versión de producción. Estas pruebas de depuración solo ocultan posibles errores de los desarrolladores para que no se encuentren hasta más adelante en el proceso.

Jimmy Hoffa
fuente
44
Estoy totalmente de acuerdo contigo Jimmy. Si usa DI y se burla de sus pruebas, ¿por qué necesitaría #if debugo una construcción similar en su código?
Richard Ev
@RichardEv Puede haber una mejor manera de manejar esto, pero actualmente la estoy usando para permitirme jugar el papel de diferentes usuarios a través de una cadena de consulta. No quiero que esto esté en producción, pero sí lo quiero para la depuración para poder controlar el flujo de trabajo que se avanza sin tener que crear múltiples usuarios e iniciar sesión en ambas cuentas para recorrer el flujo. Aunque esta es la primera vez que tengo que usarlo.
Tony
44
En lugar de solo realizar pruebas, a menudo hacemos cosas como configurar un correo electrónico de destinatario predeterminado para nosotros, en compilaciones de depuración, #if DEBUGpara que no enviemos spam accidentalmente a otros mientras probamos un sistema que debe transmitir correos electrónicos como parte del proceso. A veces estas son las herramientas adecuadas para el trabajo :)
Gone Coding
66
En general, estaría de acuerdo con usted, pero si se encuentra en una situación en la que el rendimiento es primordial, entonces no desea saturar el código con registros extraños y resultados del usuario, pero estoy 100% de acuerdo en que nunca deberían usarse para alterar el comportamiento fundamental
MikeT
55
-1 No hay nada de malo en usar cualquiera de estos. Reclamar pruebas unitarias y DI de alguna manera reemplaza una compilación habilitada de depuración de un producto es ingenua
Ted Bigham
15

Este también puede ser útil:

if (Debugger.IsAttached)
{
...
}
sofsntp
fuente
1
Personalmente, no veo cómo esto puede ser útil en comparación con las otras 2 alternativas. Esto garantiza que se compila todo el bloque y se Debugger.IsAttacheddebe invocar en tiempo de ejecución incluso en versiones de lanzamiento.
Jai
9

Con el primer ejemplo, SetPrivateValueno existirá en la compilación si DEBUGno está definido, con el segundo ejemplo, las llamadas a SetPrivateValueno existirán en la compilación si DEBUGno está definido.

Con el primer ejemplo, usted tiene que envolver cualquier llamada a SetPrivateValuela #if DEBUGtambién.

Con el segundo ejemplo, SetPrivateValuese omitirán las llamadas a , pero tenga en cuenta que SetPrivateValueaún se compilará. Esto es útil si está creando una biblioteca, por lo que una aplicación que haga referencia a su biblioteca aún puede usar su función (si se cumple la condición).

Si desea omitir las llamadas y guardar el espacio de la persona que llama, puede utilizar una combinación de las dos técnicas:

[System.Diagnostics.Conditional("DEBUG")]
public void SetPrivateValue(int value){
    #if DEBUG
    // method body here
    #endif
}
Papi
fuente
@P papá: envolver #if DEBUGalrededor Conditional("DEBUG")no elimina las llamadas a esta función, sólo se elimina la función de IL alltogether, por lo que todavía tiene las llamadas a función que no existen (errores de compilación).
mi
1
Si uno no quiere que el código exista en la versión, debe envolver el cuerpo del método en "#if DEBUG", posiblemente con un trozo "#else" (con un valor de retorno de lanzamiento o ficticio), y use el atributo para sugerir que las personas que llaman no se molestan con la llamada? Eso parecería lo mejor de ambos mundos.
supercat
@myermian, @supercat: Sí, ambos tienen razón. Mi error. Lo editaré según la sugerencia de supercat.
P Daddy
5

Supongamos que su código también tenía una #elsedeclaración que definía una función de código auxiliar nulo, abordando uno de los puntos de Jon Skeet. Hay una segunda distinción importante entre los dos.

Suponga que la función #if DEBUGo Conditionalexiste en una DLL a la que hace referencia el ejecutable de su proyecto principal. Usando #if, la evaluación del condicional se realizará con respecto a la configuración de compilación de la biblioteca. Usando el Conditionalatributo, la evaluación del condicional se realizará con respecto a la configuración de compilación del invocador.

Kennet Belenky
fuente
2

Tengo una extensión SOAP WebService para registrar el tráfico de red usando una costumbre [TraceExtension]. Lo uso solo para las versiones de depuración y omito las versiones de lanzamiento . Use #if DEBUGpara envolver el [TraceExtension]atributo y así eliminarlo de las versiones de lanzamiento .

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

Por lo general, lo necesitaría en Program.cs, donde desea decidir ejecutar el código Debug on Non-Debug y eso también principalmente en los Servicios de Windows. Así que creé un campo de solo lectura IsDebugMode y establecí su valor en el constructor estático como se muestra a continuación.

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
fuente