Inicio > .NET Framework, Apunte, C# > C# 101: directivas de preprocesador

C# 101: directivas de preprocesador


Recuerdo mi época de programador de C++. No fue hace mucho, mi última aplicación la terminé hace unas tres semanas. Quizás debí decir: "recuerdo cuando comenzaba a programar en C++", y entonces sí nos vamos 10 años atrás… En fin, como sea. En C y C++, uno podía ver código como este:

#ifdef _WIN32
#ifdef UNICODE
#define _tcscpy wcscpy
#elif
#define _tcscpy strcpy
#endif
#endif

Todas las directivas que comienzan con un gato (singo de libra, de número o como se llame en tu villa) son directivas de pre-procesamiento. Esto quiere decir que hay un software antes que el compilador, que básicamente sustituye los símbolos (llamados macro) por otros, es decir, los pre-procesa. En el caso anterior, cualquier llamada a _tcscpy se sustituido por wcscpy o por strcpy, dependiendo de si el símbolo _WIN32 y el símbolo UNICODE están definidos.

En fin, en C# también tenemos directivas de pre-procesamiento, aunque en realidad es el mismo compilador. A diferencia de C y C++, no pueden crearse macros. Pero sí se puede utilizar para crear compilaciones condicionales, que nos ayudan a generar diferentes ensamblados.

Definición de símbolos

Las primeras directivas que veremos son las directivas para definir símbolos. Estas son #define y #undef. A diferencia de C y C++, #define no asigna valor a una macro, sino que simplemente define el símbolo. Éste podrá ser utilizado después, pero hasta ahí.

#define DEBUG

public void TraceDebugInfo()
{
}

#undef DEBUG

En el ejemplo anterior, definimos el símbolo DEBUG, y luego lo indefinimos. Estas directivas no son muy útiles por sí mismas, pero se convierten en útiles cuando las combinamos con directivas de decisión.

Definir un símbolo con #define es equivalente a definir un símbolo al momento de compilar mediante el parámetro /define.

Decisión de compilación

La decisión de compilación significa que una porción determinada de código se compila si se cumple una condición en particular. La condición que debe cumplirse es si un símbolo está definido o no. Pueden concatenarse símbolos con los operadores lógicos &&, || y !, pero a eso se limita. Ahora sí hace más sentido el #define, ¿no?

public void Trace(string msg)
{

#if DEBUG && ENABLE_TRACE
    Debug.WriteLine(msg);
#elif !DEBUG && ENABLE_TRACE
    File.WriteAllText("C:\\log\\debug.log", msg);
#else
    Console.WriteLine(msg);
#endif

}

#if es la directiva para evaluar un símbolo. Si la expresión se evalúa a true, todo lo que haya después y hasta alguna otra directiva complementaria, se compila. Si no, no. Tanto #else como #elif se usan como alternativas, pero #elif permite poner una expresión a evaluar, mientras que #else no. Finalmente, el bloque debe cerrarse con un #endif.

En el ejemplo anterior, si compilamos con /define:DEBUG /define:ENABLE_TRACE, el mensaje se imprimirá en la consola de depuración (útil sólo cuando depuramos desde Visual Studio). Si compilamos con /define:ENABLE_TRACE solito, escribimos todo a un archivo de texto. Si no definimos ENABLE_TRACE, entonces escribimos en la consola de la aplicación. Por supuesto, en lugar de la compilación con /define, podemos usar #define.

Hemos hablado de cómo compilar con /define para definir símbolos, y por tanto utilizar las directivas #if, #elif, #else, #endif. Realmente así es como hace sentido, pues hacer los #defines no siempre es muy práctico, pues un cambio implica modificar el código fuente.

Visual Studio incluye un componente llamado Configuration Management. Este componente permite definir construcciones (builds). Cada construcción define qué ensamblados va a compilar, qué ensamblados referenciar, y otras configuraciones de compilación, etc. De hecho, por defecto, el Visual Studio nos genera un par de construcciones: Debug y Release. Si comparas ambas, verás que Debug tiene opciones de depuración habilitadas, que no suelen ser necesarias en la versión de producción.

Pues bien, entre las cosas que nos permite configurar están precisamente los símbolos con los que queremos compilar. En las propiedades del proyecto, tab Build, nos aparecen las configuraciones existentes. Abajo, aparece "Conditional combination symbols", ahí podemos escribir los símbolos que queramos definir. Adicionalmente, tenemos dos opciones: definir la constante DEBUG, que ya conocimos, y la constante TRACE.

clip_image001[4]

Adicional a las construcciones predefinidas, podemos crear las nuestras personalizadas. Quizás queremos establecer símbolos para diferentes plataformas (Windows, Silverlight, 32 vs 64 bits, etc.). Para crearlas, seguimos estos pasos.

1.- Seleccionar la solución o proyecto en el Solution Explorer.

2.- Seleccionar el menú Build y escoger la opción Configuration Manager.

3.- En Active Solution Configuration, seleccionamos la opción "<New>". Con esto nos pedirá el nombre y si queremos basarnos en alguna configuración. Terminando, hacemos clic en OK.

4.- Una vez creada, ya podemos cambiar la configuración que hemos creado.

Más información sobre este tema:

1.- how to: create and edit configurations.

2.- how to: modify project properties and configuration settings

Advertencias y errores

A veces querremos indicar alguna condición bajo la cuál queramos lanzar una advertencia o algún error. Por ejemplo, supongamos que todavía no hemos probado el código en modo de depuración. Entonces queremos lanzar una advertencia al compilar.

#if !DEBUG

#warning No hemos probado este código en modo Release. ¡Aguas!

#endif

O por ejemplo, nuestro código no va a soportar aplicaciones de 32 bits para Windows NT. Podemos definir símbolos con /define: para Windows NT.

#if WINNT && PLATFORM_x86

#error No se soporta WINNT en 32 bits. 


#endif

Vemos que se usa #warning y #error para ello, seguido del mensaje que queramos mostrar.

Por otro lado, tenemos que el compilador de C# genera muchas advertencias. Esto, independientemente de los errores, por supuesto. El compilador tiene diferentes niveles de advertencias, y la finalidad de éstas consiste en que los programadores reciban recordatorios sobre potenciales problemas detectados en código, o posibles omisiones. Considera este método:

void foo()
{
    int i = 0;

    throw new Exception();

    goo();
}

Este código genera dos advertencias. En primer lugar, nos dice que la variable i es inicializada pero nunca utilizada. Lo cual tiene sentido: ¿para qué declarar una variable si nunca la utilizas? Por otra parte, también nos da otra advertencia: el método foo tiene código inaccesible que nunca se ejecuta. En efecto, dado que el throw hace que la función termine anticipadamente, goo nunca será ejecutada. De ahí la advertencia.

Usualmente las advertencias deben ser corregidas. Pero en dado caso de que el código que provoca la advertencia sea intencional, entonces podemos deshabilitar la advertencia para que el compilador no la muestre. Para ello usamos la directiva #pragma warning disable, seguida del número de advertencia que queremos deshabilitar.

class Base
{
    public virtual void foo() { }
}

class Derivada : Base
{
#pragma warning disable 0108 

    // 'member1' hides inherited member 'member2'. Use the new keyword 
    // if hiding was intended.
    public void foo()

#pragma warning enable 0108
}

Vemos también que las advertencias pueden ser habilitadas de nuevo usando el #pragma warning enable, seguido del número de advertencia.

Regiones

En C# podemos crear regiones de código y asignarles una descripción. Esto lo logramos mediante las directivas #region y #endregion.

class Employee
{
#region Atributos
    private int _id;
    private string _name;
    private double _income;
#endregion

#region Propiedades
    public int ID { … }
    public string Name { … }
    public double Income { … }
#endregion

#region Métodos
    public void CalcIncome(DateTime from, DateTime until) { … }
    public override string ToString() { … }
#endregion
}

Esta es una forma de utilizarse. La verdad es que desde aquí no se ve que haga nada. Y de hecho no hacen nada: las regiones sirven para dividir el código, pero no afecta al compilador. De hecho sólo tienen utilidad dentro de Visual Studio. El Visual Studio permitirá expandir y contraer regiones, haciendo más fácil la navegación en el IDE.

Misceláneos

Existen un par de directivas misceláneas. La directiva #line permite cambiar la línea del código fuente, afectando los mensajes de advertencia y errores de compilación que puedan suceder.

void foo() 
{
    // CS0429
    if (false) {
        Console.WriteLine("Foo");
    }

}

El código anterior nos muestra lo siguiente, suponiendo que void foo sea la primera línea:

Warning CS0429: Unreachable expression code detected in file ‘File.cs’ line ‘5’

Ahora usemos la directiva #line:

#line 37
void foo() 
{
    // CS0429
    if (false) {
        Console.WriteLine("Foo");
    }
}

Ahora tenemos:

Warning CS0429: Unreachable expression code detected in file ‘File.cs’ line ’42’

Con #line, movimos el número de línea. Ahora, ¿cómo para qué sirve esto? Ni idea. Pero lo puedes hacer, si quieres sacarle canas blancas al resto de tu equipo de trabajo.

La otra directiva miscelánea es el #pragma checksum. Esta directiva genera "checksums" para un archivo determinado. Un checksum es un número o identificador que se asigna a algún elemento, para comprobar que se esté utilizando la última versión. En particular, esto es útil para págians de ASP.NET, ya que los ASPX están separados de los ensamblados.

Este es un ejemplo:

#pragma checksum "file.cs" "{3673e4ca-6098-4ec1-890f-8fceb2a794a2}" 
     "{012345678AB}" 

That’s that!

Pues eso es todo. Esas son todas las directivas de preprocesador existentes en C# hasta la versión actual. Ciertamente la parte importante es #if / #endif y #define. Se les puede sacar mucho provecho. Sin embargo, no hay que abusar: un error común que no queremos heredar de C y C++ es precisamente el llenarnos de directivas.

Categorías:.NET Framework, Apunte, C# Etiquetas: , , ,
  1. Aún no hay comentarios.
  1. No trackbacks yet.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s