Compartir a través de


Directivas de preprocesador de C#

Aunque el compilador no tiene un preprocesador independiente, procesa las directivas descritas en esta sección como si hubiera una. Use estas directivas para ayudar en la compilación condicional. A diferencia de las directivas de C y C++, estas no se pueden usar para crear macros. Una directiva de preprocesador debe ser la única instrucción en una línea.

La referencia del lenguaje C# documenta la versión publicada más recientemente del lenguaje C#. También contiene documentación inicial sobre las características de las versiones preliminares públicas de la próxima versión del lenguaje.

La documentación identifica cualquier característica introducida por primera vez en las últimas tres versiones del idioma o en las versiones preliminares públicas actuales.

Sugerencia

Para buscar cuándo se introdujo por primera vez una característica en C#, consulte el artículo sobre el historial de versiones del lenguaje C#.

Aplicaciones basadas en archivos

Las aplicaciones basadas en archivos son programas que se compilan y ejecutan mediante dotnet run Program.cs (o cualquier *.cs archivo). El compilador de C# omite estas directivas de preprocesador, pero el sistema de compilación los analiza para generar la salida. Estas directivas generan advertencias cuando se encuentran en una compilación basada en proyectos.

El compilador de C# omite cualquier directiva de preprocesador que comience por #: o #!.

La #! directiva de preprocesador permite que los shells de Unix ejecuten directamente un archivo de C# mediante dotnet run. Por ejemplo:

#!/usr/bin/env dotnet run
Console.WriteLine("Hello");

El fragmento de código anterior informa a un shell de Unix para ejecutar el archivo mediante dotnet run. El /usr/bin/env comando localiza el dotnet archivo ejecutable en path, lo que hace que este enfoque sea portátil en diferentes distribuciones de Unix y macOS. La #! línea debe ser la primera línea del archivo y los siguientes tokens son el programa que se va a ejecutar. Debe habilitar el permiso execute (x) en el archivo de C# para esa característica.

Las #: directivas que se usan en aplicaciones basadas en archivos se describen en la referencia de aplicaciones basadas en archivos.

Otras herramientas pueden agregar nuevos tokens después de la #: convención.

Contexto que admite un valor NULL

La directiva de preprocesador #nullable establece las anotaciones y marcas de advertencia en el contexto que admite valores NULL. Esta directiva controla si las anotaciones que admiten un valor NULL surten algún efecto y si se proporcionan advertencias de nulabilidad. Cada marca está deshabilitada o habilitada.

Puede especificar ambos contextos en el nivel de proyecto (fuera del código fuente de C#) agregando el Nullable elemento al PropertyGroup elemento . La directiva #nullable controla las marcas de anotación y advertencia y tiene prioridad sobre la configuración de nivel de proyecto. Una directiva establece la marca que controla hasta que otra directiva la invalida o hasta el final del archivo de origen.

El efecto de las directivas es el siguiente:

  • #nullable disable: establece el contexto que admite valores NULL en deshabilitado.
  • #nullable enable: establece el contexto que admite valores NULL en habilitado.
  • #nullable restore: restaura el contexto que admite valores NULL con la configuración del proyecto.
  • #nullable disable annotations: establece la marca de anotaciones en el contexto que admite valores NULL en deshabilitada.
  • #nullable enable annotations: establece la marca de anotaciones en el contexto que admite valores NULL en habilitada.
  • #nullable restore annotations: restaura la marca de anotaciones en el contexto que admite valores NULL con la configuración del proyecto.
  • #nullable disable warnings: establece la marca de advertencia en el contexto que admite valores NULL en deshabilitada.
  • #nullable enable warnings: establece la marca de advertencia en el contexto que admite valores NULL en habilitada.
  • #nullable restore warnings: restaura la marca de advertencia en el contexto que admite valores NULL con la configuración del proyecto.

Compilación condicional

Use cuatro directivas de preprocesador para controlar la compilación condicional:

  • #if: inicia una compilación condicional. El compilador compila el código solo si se define el símbolo especificado.
  • #elif: cierra la compilación condicional anterior y abre una nueva en función de si se define el símbolo especificado.
  • #else: cierra la compilación condicional anterior y abre una nueva si no se ha definido el símbolo especificado anterior.
  • #endif: cierra la compilación condicional anterior.

El sistema de compilación también tiene en cuenta los símbolos de preprocesador predefinidos que representan distintos marcos de destino en proyectos de estilo SDK. Resultan útiles al crear aplicaciones que pueden tener como destino más de una versión de .NET.

Versiones de .NET Framework de destino Símbolos Símbolos adicionales
(disponible en SDK de .NET 5+)
Símbolos de plataforma (solo disponibles
cuando se especifica un TFM específico del sistema operativo)
.NET Framework NETFRAMEWORK, NET481, NET48, NET472, , NET471NET47, NET462, NET461, NET46NET452NET451NET45NET40NET35NET20 NET48_OR_GREATER, NET472_OR_GREATER, NET471_OR_GREATER, NET47_OR_GREATER, , NET462_OR_GREATERNET461_OR_GREATER, NET46_OR_GREATER, NET452_OR_GREATERNET451_OR_GREATERNET45_OR_GREATERNET40_OR_GREATERNET35_OR_GREATERNET20_OR_GREATER
.NET Standard NETSTANDARD, NETSTANDARD2_1, NETSTANDARD2_0, NETSTANDARD1_6, NETSTANDARD1_5, NETSTANDARD1_4, NETSTANDARD1_3, NETSTANDARD1_2, , NETSTANDARD1_1NETSTANDARD1_0 NETSTANDARD2_1_OR_GREATER, NETSTANDARD2_0_OR_GREATER, NETSTANDARD1_6_OR_GREATER, NETSTANDARD1_5_OR_GREATER, NETSTANDARD1_4_OR_GREATER, NETSTANDARD1_3_OR_GREATER, NETSTANDARD1_2_OR_GREATER, NETSTANDARD1_1_OR_GREATERNETSTANDARD1_0_OR_GREATER
.NET 5+ (y .NET Core) NET, NET10_0, NET9_0, NET8_0, , NET7_0NET6_0, NET5_0, NETCOREAPP, NETCOREAPP3_1NETCOREAPP3_0NETCOREAPP2_2NETCOREAPP2_1NETCOREAPP2_0NETCOREAPP1_1NETCOREAPP1_0 NET10_0_OR_GREATER, NET9_0_OR_GREATER, NET8_0_OR_GREATER, NET7_0_OR_GREATER, , NET6_0_OR_GREATERNET5_0_OR_GREATER, NETCOREAPP3_1_OR_GREATER, NETCOREAPP3_0_OR_GREATERNETCOREAPP2_2_OR_GREATERNETCOREAPP2_1_OR_GREATERNETCOREAPP2_0_OR_GREATERNETCOREAPP1_1_OR_GREATERNETCOREAPP1_0_OR_GREATER ANDROID, BROWSER, IOS, MACCATALYST, MACOS, TVOS, , WINDOWS
[OS][version] (por ejemplo IOS15_1),
[OS][version]_OR_GREATER (por ejemplo IOS15_1_OR_GREATER)

Nota:

  • Los símbolos sin versión se definen independientemente de la versión de destino.
  • Los símbolos específicos de la versión solo se definen para la versión de destino.
  • Los símbolos <framework>_OR_GREATER se definen para la versión de destino y todas las versiones anteriores. Por ejemplo, si tiene como destino .NET Framework 2.0, se definen los símbolos siguientes: NET20, NET20_OR_GREATER, NET11_OR_GREATER y NET10_OR_GREATER.
  • Los símbolos NETSTANDARD<x>_<y>_OR_GREATER solo se definen para destinos de .NET Standard y no para destinos que implementan .NET Standard, como .NET Core y .NET Framework.
  • Son diferentes de los monikers de la plataforma de destino (TFM) que usa la propiedad MSBuildTargetFramework y NuGet.

Nota:

En el caso de los proyectos que no son de estilo SDK, tendrá que configurar manualmente los símbolos de compilación condicional para las diferentes plataformas de destino en Visual Studio a través de las páginas de propiedades del proyecto.

Otros símbolos predefinidos incluyen las constantes DEBUG y TRACE. Use #define para invalidar los valores establecidos para el proyecto. El DEBUG símbolo, por ejemplo, se establece automáticamente en función de las propiedades de configuración de compilación ("Depurar" o "Versión").

El compilador de C# compila el código entre la directiva #if y la directiva #endif solo si se define el símbolo especificado o no se define cuando se usa el operador not !. A diferencia de C y C++, no se puede asignar un valor numérico a un símbolo. La instrucción #if de C# es booleana y solo comprueba si el símbolo está definido o no. Por ejemplo, el código siguiente se compila cuando DEBUG se define:

#if DEBUG
    Console.WriteLine("Debug version");
#endif

El código siguiente se compila cuando MYTESTno se define:

#if !MYTEST
    Console.WriteLine("MYTEST is not defined");
#endif

Use los operadores == (igualdad) y != (desigualdad) para probar los bool valores true o false. true significa que el símbolo está definido. La instrucción #if DEBUG tiene el mismo significado que #if (DEBUG == true). Use los && operadores (y), (o)|| y ! (no) para evaluar si se definen varios símbolos. Es posible agrupar símbolos y operadores mediante paréntesis.

En el ejemplo siguiente se muestra una directiva compleja que permite que el código aproveche las características más recientes de .NET mientras permanece compatible con versiones anteriores. Por ejemplo, imagine que usa un paquete NuGet en el código, pero el paquete solo admite .NET 6 y versiones posteriores, así como .NET Standard 2.0 y versiones posteriores:

#if (NET6_0_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
    Console.WriteLine("Using .NET 6+ or .NET Standard 2+ code.");
#else
    Console.WriteLine("Using older code that doesn't support the above .NET versions.");
#endif

#if, junto con las directivas #else, #elif, #endif, #define y #undef, permite incluir o excluir código basado en la existencia de uno o varios símbolos. La compilación condicional puede resultar útil al compilar código para una compilación de depuración o para una configuración concreta.

#elif permite crear una directiva condicional compuesta. El compilador evalúa la #elif expresión si ni las expresiones de directiva anteriores #if ni anteriores, opcionales, #elif se evalúan como true. Si una expresión #elif se evalúa como true, el compilador evalúa todo el código comprendido entre #elif y la siguiente directiva condicional. Por ejemplo:

#define VC7
//...
#if DEBUG
    Console.WriteLine("Debug build");
#elif VC7
    Console.WriteLine("Visual Studio 7");
#endif

#else permite crear una directiva condicional compuesta. Si ninguna de las expresiones de las directivas anteriores #if o (opcionales) #elif se evalúa como true, el compilador evalúa todo el código entre #else y el siguiente #endif. #endif debe ser la siguiente directiva de preprocesador después #elsede .

#endif especifica el final de una directiva condicional, que comienza con la directiva #if.

En el ejemplo siguiente se muestra cómo definir un MYTEST símbolo en un archivo y, a continuación, probar los valores de los MYTEST símbolos y DEBUG . La salida de este ejemplo depende de si ha compilado el proyecto en modo de configuración Depuración o Versión .

#define MYTEST
using System;
public class MyClass
{
    static void Main()
    {
#if (DEBUG && !MYTEST)
        Console.WriteLine("DEBUG is defined");
#elif (!DEBUG && MYTEST)
        Console.WriteLine("MYTEST is defined");
#elif (DEBUG && MYTEST)
        Console.WriteLine("DEBUG and MYTEST are defined");
#else
        Console.WriteLine("DEBUG and MYTEST are not defined");
#endif
    }
}

En el ejemplo siguiente se muestra cómo probar diferentes marcos de destino para que pueda usar las API más recientes cuando sea posible:

public class MyClass
{
    static void Main()
    {
#if NET40
        WebClient _client = new WebClient();
#else
        HttpClient _client = new HttpClient();
#endif
    }
    //...
}

Definición de símbolos

Use las dos directivas de preprocesador siguientes para definir o anular la definición de símbolos para la compilación condicional:

  • #define: se define un símbolo.
  • #undef: se anula la definición de un símbolo.

Use #define para definir un símbolo. Cuando se usa el símbolo como expresión que se pasa a la directiva #if, la expresión se evalúa como true, como se muestra en el ejemplo siguiente:

#define VERBOSE

#if VERBOSE
   Console.WriteLine("Verbose output version");
#endif

Nota:

En C#, las constantes primitivas deben definirse mediante la const palabra clave . Una declaración const crea un miembro static que no se puede modificar en tiempo de ejecución. La directiva #define no puede usarse para declarar valores constantes como suele hacerse en C y C++. Si tiene varias constantes de este tipo, puede considerar la posibilidad de crear una clase "Constants" independiente donde incluirlas.

Use símbolos para especificar condiciones para la compilación. Pruebe el símbolo mediante #if o #elif. También se puede usar ConditionalAttribute para realizar una compilación condicional. Puede definir un símbolo, pero no asignar un valor a un símbolo. La directiva #define debe aparecer en el archivo antes de que use cualquier instrucción que tampoco sea una directiva del preprocesador. También puede definir un símbolo mediante la opción del compilador DefineConstants . Anulación de definición de un símbolo mediante #undef.

Definición de regiones

Defina las regiones de código que puede contraer en un esquema mediante las dos directivas de preprocesador siguientes:

  • #region: se inicia una región.
  • #endregion: se finaliza una región.

#region permite especificar un bloque de código que se puede expandir o contraer cuando se usa la característica de esquematización del editor de código. En archivos de código más largos, es conveniente contraer u ocultar una o varias regiones para poder centrarse en la parte del archivo en la que se trabaja actualmente. En el ejemplo siguiente se muestra cómo definir una región:

#region MyClass definition
public class MyClass
{
    static void Main()
    {
    }
}
#endregion

Un bloque #region se debe terminar con una directiva #endregion. Un bloque #region no se puede superponer con un bloque #if. Pero, un bloque #region se puede anidar en un bloque #if y un bloque #if se puede anidar en un bloque #region.

Información de errores y advertencias

Puede usar las siguientes directivas para generar advertencias y errores del compilador definidos por el usuario, así como información de línea de control:

  • #error: se genera un error del compilador con un mensaje especificado.
  • #warning: se genera una advertencia del compilador con un mensaje especificado.
  • #line: se cambia el número de línea impreso con mensajes del compilador.

Use #error para generar un error definido por el usuario CS1029 a partir de una ubicación específica en el código. Por ejemplo:

#error Deprecated code in this method.

Nota:

El compilador trata #error version de forma especial e informa de un error del compilador, CS8304, con un mensaje que contiene las versiones que se usan del compilador y del lenguaje.

Use #warning para generar una advertencia del compilador de nivel CS1030 a partir de una ubicación específica del código. Por ejemplo:

#warning Deprecated code in this method.

Use #line para modificar la numeración de líneas del compilador y (opcionalmente) la salida del nombre de archivo para errores y advertencias.

En el siguiente ejemplo, se muestra cómo notificar dos advertencias asociadas con números de línea. La #line 200 directiva obliga a que el número de la línea siguiente sea 200 (aunque el valor predeterminado es #6) y hasta la siguiente #line directiva, el nombre de archivo se notifica como "Especial". La #line default directiva devuelve la numeración de líneas a su numeración predeterminada, que cuenta las líneas numeradas de nuevo por la directiva anterior.

class MainClass
{
    static void Main()
    {
#line 200 "Special"
        int i;
        int j;
#line default
        char c;
        float f;
#line hidden // numbering not affected
        string s;
        double d;
    }
}

La compilación genera el siguiente resultado:

Special(200,13): warning CS0168: The variable 'i' is declared but never used
Special(201,13): warning CS0168: The variable 'j' is declared but never used
MainClass.cs(9,14): warning CS0168: The variable 'c' is declared but never used
MainClass.cs(10,15): warning CS0168: The variable 'f' is declared but never used
MainClass.cs(12,16): warning CS0168: The variable 's' is declared but never used
MainClass.cs(13,16): warning CS0168: The variable 'd' is declared but never used

La directiva #line podría usarse en un paso intermedio automatizado en el proceso de compilación. Por ejemplo, si quita líneas del archivo de código fuente original, pero desea que el compilador genere la salida en función de la numeración de línea original en el archivo, puede quitar líneas y, a continuación, simular la numeración de líneas original mediante #line.

La #line hidden directiva oculta las líneas sucesivas del depurador, de modo que, cuando el desarrollador recorre el código, las líneas entre una #line hidden y la siguiente #line directiva (suponiendo que no es otra #line hidden directiva) se intercalarán. Esta opción también se puede usar para permitir que ASP.NET diferencie entre el código generado por el equipo y el definido por el usuario. Aunque ASP.NET es el consumidor principal de esta característica, es probable que más generadores de origen lo usen.

Una directiva #line hidden no afecta a los nombres de archivo ni a los números de línea en el informe de errores. Es decir, si el compilador encuentra un error en un bloque oculto, el compilador notifica el nombre de archivo actual y el número de línea del error.

La directiva #line filename especifica el nombre de archivo que quiere que aparezca en la salida del compilador. De forma predeterminada, se usa el nombre real del archivo de código fuente. El nombre de archivo debe estar entre comillas dobles ("") y debe seguir un número de línea.

Puede usar una nueva forma de la directiva #line:

#line (1, 1) - (5, 60) 10 "partial-class.cs"
/*34567*/int b = 0;

Los componentes de este formulario son los siguientes:

  • (1, 1): la línea de inicio y la columna del primer carácter de la línea que sigue a la directiva. En este ejemplo, la línea siguiente se notifica como línea 1, columna 1.
  • (5, 60): la línea final y la columna de la región marcada.
  • 10: desplazamiento de columna para que la directiva #line surta efecto. En este ejemplo, la 10ª columna se notifica como columna uno. La declaración int b = 0; comienza en esa columna. Este campo es opcional. Si se omite, la directiva surte efecto en la primera columna.
  • "partial-class.cs": el nombre del archivo de salida.

En el ejemplo anterior se genera la advertencia siguiente:

partial-class.cs(1,5,1,6): warning CS0219: The variable 'b' is assigned but its value is never used

Después de reasignar, la variable b se encuentra en la primera línea, en el carácter seis, del archivo partial-class.cs.

Los lenguajes específicos de dominio (DSL) suelen usar este formato para proporcionar una mejor asignación desde el archivo de origen hasta la salida de C# generada. El uso más común de esta directiva extendida #line es reasignar advertencias o errores que aparecen en un archivo generado al origen original. Por ejemplo, considere esta página de Razor:

@page "/"
Time: @DateTime.NowAndThen

La propiedad DateTime.Now se escribe incorrectamente como DateTime.NowAndThen. El C# generado para este fragmento de código de Razor tiene el siguiente aspecto, en page.g.cs:

  _builder.Add("Time: ");
#line (2, 6) - (2, 27) 15 "page.razor"
  _builder.Add(DateTime.NowAndThen);

La salida del compilador para el fragmento de código anterior es:

page.razor(2, 2, 2, 27)error CS0117: 'DateTime' does not contain a definition for 'NowAndThen'

La línea 2, columna 6 de page.razor es donde comienza el texto @DateTime.NowAndThen, indicado por (2, 6) en la directiva . Ese intervalo de @DateTime.NowAndThen termina en la línea 2, columna 27, tal como indica (2, 27) en la directiva. El texto de DateTime.NowAndThen comienza en la columna 15 de page.g.cs, como indica 15 en la directiva. El compilador notifica el error en su ubicación en page.razor. El desarrollador puede navegar directamente al error en su código fuente, no en el código fuente generado.

Para ver más ejemplos de este formato, vea la especificación de la característica en la sección sobre ejemplos.

Pragmas

El compilador usa #pragma para obtener instrucciones especiales para compilar el archivo donde aparece. El compilador debe admitir las pragmas que use. Es decir, no puede usar #pragma para crear instrucciones de preprocesamiento personalizadas.

#pragma pragma-name pragma-arguments

pragma-name es el nombre de una pragma reconocida. pragma-arguments es los argumentos específicos de pragma.

advertencia de #pragma

#pragma warning puede habilitar o deshabilitar determinadas advertencias. Los #pragma warning disable format y #pragma warning enable format controlan cómo Visual Studio da formato a los bloques de código.

#pragma warning disable warning-list
#pragma warning restore warning-list

warning-list es una lista separada por comas de números de advertencia, como 414, CS3021. El prefijo "CS" es opcional. Cuando no se especifican números de advertencia, disable deshabilita todas las advertencias y restore habilita todas las advertencias.

Nota:

Para buscar los números de advertencia en Visual Studio, compile el proyecto y después busque los números de advertencia en la ventana Salida.

disable surte efecto a partir de la siguiente línea del archivo de código fuente. La advertencia se restaura en la línea que sigue a restore. Si no hay ningún restore en el archivo, las advertencias se restauran a su estado predeterminado en la primera línea de los archivos posteriores de la misma compilación.

// pragma_warning.cs
using System;

#pragma warning disable 414, CS3021
[CLSCompliant(false)]
public class C
{
    int i = 1;
    static void Main()
    {
    }
}
#pragma warning restore CS3021
[CLSCompliant(false)]  // CS3021
public class D
{
    int i = 1;
    public static void F()
    {
    }
}

Otra forma de warning pragma deshabilita o restaura comandos de formato de Visual Studio en bloques de código:

#pragma warning disable format
#pragma warning restore format

Los comandos de formato de Visual Studio no modifican el texto en bloques de código donde disable format está en vigor. Los comandos de formato, como Ctrl+K, Ctrl+D, no modifican esas regiones de código. Esta pragma proporciona un control preciso sobre la presentación visual del código.

Suma de comprobación #pragma

Genera sumas de comprobación para los archivos de origen para ayudar con la depuración de páginas ASP.NET.

#pragma checksum "filename" "{guid}" "checksum bytes"

La directiva usa "filename" como nombre del archivo para supervisar los cambios o actualizaciones, "{guid}" como identificador único global (GUID) para el algoritmo hash y "checksum_bytes" como la cadena de dígitos hexadecimales que representan los bytes de la suma de comprobación. Debe proporcionar un número par de dígitos hexadecimales. Un número impar de dígitos genera una advertencia de tiempo de compilación y la directiva se ignora.

El depurador de Visual Studio usa una suma de comprobación para asegurarse de que siempre encuentra el código fuente correcto. El compilador calcula la suma de comprobación para un archivo de origen y, después, emite el resultado en el archivo de base de datos del programa (PDB). El depurador usa el PDB para comparar con la suma de comprobación que calcula para el archivo de origen.

Esta solución no funciona con proyectos de ASP.NET, ya que la suma de comprobación calculada es para el archivo de código fuente generado, no para el archivo .aspx. Para solucionar este problema, #pragma checksum proporciona compatibilidad de la suma de comprobación con páginas ASP.NET.

Cuando se crea un proyecto de ASP.NET en Visual C#, el archivo de código fuente generado contiene una suma de comprobación del archivo .aspx desde el que se genera el código fuente. Después, el compilador escribe esta información en el archivo PDB.

Si el compilador no encuentra ninguna directiva #pragma checksum en el archivo, calcula la suma de comprobación y escribe el valor en el archivo PDB.

class TestClass
{
    static int Main()
    {
        #pragma checksum "file.cs" "{406EA660-64CF-4C82-B6F0-42D48172A799}" "ab007f1d23d9" // New checksum
    }
}