Il precompilatore è presente da molto tempo in linguaggi come il C/C++, esso viene utilizzato per risolvere il problema della differenziazione del proprio software di base, ad esempio un codice scritto per più sistemi operativi. Pensiamo ad un problema banale, scrivere una funzione che crei un file e vi inserisca una frase. I processi di input ed output sono diversi da sistema a sistema, ecco perché si utilizzano le direttive del precompilatore per avere versioni differenti dello stesso programma.
Con l’introduzione del bytecode tutto questo sembrerebbe inutile, in quanto il compilatore crea un codice che gira su di una macchina virtuale diversa a seconda del sistema operativo, ecco perché Java all’inizio non l’ha adottato, C# al contrario si. Infatti le istruzioni del precompilatore possono servire per creare diverse versioni del programma, ad esempio la versione debug, demo, di prova o completa. Ma vediamo le direttive del precompilatore che si possono utilizzare in C#.
La direttiva principale del preprocessore è #define che serve per definire un’etichetta e di cui è possibile verificarne l’esistenza. Tramite #if, #elif, #else ed #endif è possibile gestire la definizione di un’etichetta, ecco un piccolo esempio pratico.
#define DESKTOP using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TestCons { class Program { static void Main(string[] args) { #if DESKTOP System.Console.WriteLine("Questa è la versione desktop!"); #elif SERVER System.Console.WriteLine("Questa è la versione server!"); #else System.Console.WriteLine("Questa è un'altra versione!"); #endif } } }
Il costrutto #if serve per creare dei rami condizionali all’interno del codice, dato che si tratta di precompilazione, non è possibile variare il codice a runtime, visto che le parti non incluse non vengono compilate e quindi nel codice compilato finale non ci sono.
Tramite la direttiva #line è possibile modificare le informazioni sulla posizione della compilazione all’interno del codice sorgente. Se ad esempio inseriamo questa riga:
#line 100 “prova.cs”
il precompilatore inizierà a ricontare le righe come se ci trovassimo alla riga 100. Per annullare la forzatura basta inserire:
#line default
da questa riga in avanti il compilatore continuerà con la numerazione corretta.
Le direttive #warning ed #error servono proprio a quello che sembrano, la prima visualizza un messaggio di avviso durante la compilazione, mentre la seconda ferma la compilazione definitivamente.
Una direttiva abbastanza inutile è #region che serve per delimitare una regione di codice sorgente, l’unico scopo di essa è poter lavorare con l’editor solo su una parte di codice e può aiutare con l’indentazione dello stesso.
Nelle ultime versioni del C# sono state inserite anche le direttive del preprocessore forse più importanti in C/C++, il #pragma che può avere molte utilizzazioni. Ad esempio la riga:
#pragma warning disable 3021
disabilita la visualizzazione dei messaggi di warning con numero 3021 della compilazione, è utile quando si conosce l’entità del problema ed allora si evita di sporcare l’output della compilazione. Per ripristinare la visione del warning possiamo scrivere:
#pragma warning restore 3021
e dopo questa riga il compilatore inserirà di nuovo nell’output di compilazione il messaggio di warning 3021.
Per un programmatore come me, nato con il C/C++ le direttive del preprocessore sono molto importanti, infatti le utilizzo normalmente per nascondere degli avvisi che conosco già ed in questo modo posso avere l’attenzione solo sui problemi le cui conseguenze non conosco, avere questo strumento anche in C# è molto importante. Per finire, le direttive del precompilatore sono basilari per fare la compilazione condizionata e la creazione di programmi di varie versioni senza alcuno sforzo, infatti anche in C# è possibile inserire le etichette che si vogliono definire e quindi creare dei compilati diversi semplicemente cambiando l’argomento nella linea di comando del compilatore.