Como todo lenguaje de programación que se precie, C# dispone de de un preprocesador para permitir ciertas tareas que no serían posibles (o muy difíciles) sin él. Aunque técnicamente hablando no es un preprocesador, ya que dichas directivas se procesan simultáneamente durante el análisis léxico.
Y el título de esta entrada tiene una explicación: comparado con el de otros lenguajes de programación, el preprocesador de C# es un juguete; eso no quiere decir que no se puedan hacer las mismas cosas que con, por ejemplo, C++, sino que hay que hacerlas de forma diferente.
Definir una constante
Mientras que en otros lenguajes podemos definir una constante y asignarle un valor, o sólo definirla, en C# sólo podemos hacer esto último, con lo que se elimina cualquier posibilidad de sustitución de macros, tokenización, creación de funciones o la simple sustitución de un número mágico por su equivalente predefinida. C# permite que muchas de estas tareas se puedan realizar de forma más segura (y más complicada también) aprovechando que se trata de un lenguaje interpretado (no hay nada nuevo bajo el sol, y el uso del p-code se viene utilizando desde tiempos inmemoriales, quien recuerde el clipper sabrá de que estoy hablando. De hecho, a nivel teórico no existe mucha diferencia entre el basic tradicional y los modernos lenguajes .NET. Estamos hablando de un código fuente que se transforma en una serie de símbolos binarios que un motor de ejecución ha de interpretar).
Compilación condicional
Basadas en las constantes anteriormente citadas se permite lo que se conoce como compilación condicional. Dado que con el preprocesador no se puede asignar un valor a una constante, la compilación condicional sólo puede comprobar la existencia de dicha constante.
La compilación condicional puede llegar a ser enormemente útil en ciertas situaciones; desde diferenciar una construcción de depuración o final, hasta la inclusión de según qué codigo en según qué situaciones.
Un ejemplo que englobe lo visto hasta ahora podría ser el siguiente:
#define HOLA
#if HOLA
...
#elif ADIOS
...
#else
...
#endif
#undef HOLA
Nada más sencillo ni más elemental. Primero definimos la constante HOLA, luego si dicha constante está definida entramos dentro de #if HOLA, si no está definida continuamos la cadena, y en el momento en el que entremos en uno de los if, ya no se siguen evaluando más condicionales, por lo que si estuviera definido tanto HOLA como ADIOS, el compilador no entraría en en bloque ADIOS.
Directivas de diagnóstico
Aunque no suelen ser muy utilizadas en el desarrollo normal (personalmente recomiendo encarecidamente su uso junto a la compilación condicional para evitar situaciones un tanto kafkianas), sí que son enormemente útiles en el mundo de la metaprogramación. Disponemos de dos directivas,
#warning Esto es un aviso
#error Esto es un error.
La primera línea nos imprimirá un aviso como si se tratara de un aviso generado por el compilador, y la segunda un error con las mismas características que los errores del compilador.
Directiva de región
Estas son nuevas en los lenguajes .NET, y se utilizan para agrupar lógicamente bloques de código para simplificar la lectura del mismo. Personalmente las utiliza muy a menudo y me resultan muy utiles para agrupar algoritmos, separar secciones y en general presentar un código mucho más legible y bonito.
Directivas de línea
Quizás las menos utilizadas de todas, y al igual que las de diagnóstico sólo suelen utilizarse en la metaprogramación. Su función, que quizá parezca un tanto sin sentido, es la de cambiar u ocultar los números de línea del fichero de texto, para forzar los que nosotros queramos.
#line 100
#line default
#line hidden
La primera directiva cambia el número de línea por 100, la segunda restaura los números de linea y la tercera los oculta (es decir, si se produce un error o un aviso, esta información aparecerá sin número de línea). La directiva central también sirve para restaurar el estado oculto.
Conclusión
Aunque muy pobrecitas, las funciones del pseudo preprocesador de C# se muestran suficientes, pero por un justo aprobado. Lo que más echo en falta es el poder definir constantes. Ya sé que existe su equivalente dentro de una clase, pero no es lo mismo. A veces son necesarios valores globales a toda la aplicación, lo que obliga a meterlas dentro de una clase, y ya sabemos que el .NET no trabaja muy fino con los elementos estáticos. La verdas es que con el diseño del .NET y del C# se han lucido en algunos aspectos, y este es uno de ellos. En fin.