다음을 통해 공유


C# 전처리기 지시문

컴파일러에는 별도의 전처리기가 없지만, 이 단원에 설명된 지시문은 전처리기가 있는 것처럼 처리됩니다. 조건부 컴파일에서 지시문을 유용하게 사용할 수 있습니다. C 및 C++ 지시문과 달리, 매크로를 만드는 데는 해당 지시문을 사용할 수 없습니다. 전처리기 지시문은 한 줄에서 유일한 명령이어야 합니다.

Null 허용 컨텍스트

#nullable 전처리기 지시문은 ‘null 허용 주석 컨텍스트’ 및 ‘null 허용 경고 컨텍스트’를 설정합니다. 이 지시문은 null 허용 주석이 적용되는지와 null 허용 여부 경고가 지정되는지를 제어합니다. 각 컨텍스트는 disabled 또는 enabled입니다.

PropertyGroup 요소에 Nullable 요소를 추가하는 프로젝트 수준(C# 소스 코드 외부)에서 두 컨텍스트를 모두 지정할 수 있습니다. #nullable 지시문은 주석 및 경고 컨텍스트를 제어하고 프로젝트 수준 설정보다 우선으로 적용됩니다. 지시문은 다른 지시문이 재정의할 때까지 제어하는 컨텍스트를 설정하거나 소스 파일의 끝까지 설정합니다.

지시문의 효과는 다음과 같습니다.

  • #nullable disable: null 허용 주석 및 경고 컨텍스트를 사용 안 함으로 설정합니다.
  • #nullable enable: null 허용 주석 및 경고 컨텍스트를 사용함으로 설정합니다.
  • #nullable restore: null 허용 주석 및 경고 컨텍스트를 프로젝트 설정으로 복원합니다.
  • #nullable disable annotations: null 허용 주석 컨텍스트를 사용 안 함으로 설정합니다.
  • #nullable enable annotations: null 허용 주석 컨텍스트를 사용함으로 설정합니다.
  • #nullable restore annotations: null 허용 주석 컨텍스트를 프로젝트 설정으로 복원합니다.
  • #nullable disable warnings: null 허용 경고 컨텍스트를 사용 안 함으로 설정합니다.
  • #nullable enable warnings: null 허용 경고 컨텍스트를 사용함으로 설정합니다.
  • #nullable restore warnings: null 허용 경고 컨텍스트를 프로젝트 설정으로 복원합니다.

조건부 컴파일

네 가지 전처리기 지시문을 사용하여 조건부 컴파일을 제어합니다.

  • #if: 지정된 기호가 정의된 경우에만 코드가 컴파일되는 조건부 컴파일을 엽니다.
  • #elif: 앞에 있는 조건부 컴파일을 닫고 지정된 기호가 정의되었는지에 따라 새 조건부 컴파일을 엽니다.
  • #else: 앞에 있는 조건부 컴파일을 닫고 이전 지정된 기호가 정의되지 않은 경우 새 조건부 컴파일을 엽니다.
  • #endif: 앞에 있는 조건부 컴파일을 닫습니다.

C# 컴파일러는 지정된 기호가 정의되거나 ! 연산자가 사용되지 않을 때 정의되지 않은 경우에만 #if 지시문과 #endif 지시문 간의 코드를 컴파일합니다. C 및 C++와 달리 기호에 숫자 값을 할당할 수 없습니다. C#의 #if 문은 부울이고, 기호가 정의되었는지 여부만 테스트합니다. 예를 들어 다음 코드는 DEBUG(이)가 정의될 때 컴파일됩니다.

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

다음 코드는 MYTEST(이)가 정의되지 않는 경우 컴파일됩니다.

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

==(같음)!=(같지 않음) 연산자를 사용하여 booltrue 또는 false를 테스트할 수 있습니다. true가 반환되면 기호가 정의된 것입니다. #if DEBUG 문의 의미는 #if (DEBUG == true)와 같습니다. &&(and), ||(or)!(not) 연산자를 사용하여 여러 기호가 정의되었는지를 평가할 수 있습니다. 기호와 연산자를 괄호로 묶을 수도 있습니다.

다음은 이전 버전과의 호환성을 유지하면서 코드가 최신 .NET 기능을 활용할 수 있도록 하는 복잡한 지시문입니다. 예를 들어, 코드에서 NuGet 패키지를 사용하고 있지만 해당 패키지는 .NET 6 이상과 .NET Standard 2.0 이상만 지원한다고 가정해 보겠습니다.

#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(은)는 #else, #elif, #endif, #define#undef 지시문과 함께 하나 이상의 기호 유무에 따라 코드를 포함하거나 제외하도록 허용합니다. 조건부 컴파일은 코드를 디버그 빌드용으로 컴파일하거나 특정 구성용으로 컴파일할 때 유용할 수 있습니다.

#if 지시문으로 시작되는 조건부 지시문은 #endif 지시문을 사용하여 명시적으로 종료해야 합니다. #define을 사용하여 기호를 정의할 수 있습니다. #if 지시문으로 전달되는 식으로 해당 기호를 사용하면 식이 true로 평가됩니다. DefineConstants 컴파일러 옵션을 사용하여 기호를 정의할 수도 있습니다. #undef(으)로 기호 정의를 해제할 수 있습니다. #define을 사용하여 만든 기호의 범위는 해당 기호가 정의된 파일입니다. DefineConstants 또는 #define으로 정의하는 기호는 동일한 이름의 변수와 충돌하지 않습니다. 즉, 변수 이름이 전처리기 지시문에 전달되지 않아야 하며 전처리기 지시문을 통해서만 기호를 평가할 수 있습니다.

#elif를 사용하면 복합 조건부 지시문을 만들 수 있습니다. #elif 식은 앞에 있는 #if 및 모든 앞에 있는 선택적 #elif 지시문 식이 true로 평가되지 않는 경우 평가됩니다. #elif 식이 true로 평가되면 컴파일러는 #elif와 다음 조건부 지시문 사이에 있는 모든 코드를 평가합니다. 예시:

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

#else를 사용하면 복합 조건부 지시문을 만들 수 있으므로, 앞에 있는 #if 또는 (선택적) #elif 지시문의 식이 true로 평가되지 않으면 컴파일러는 #else와 다음 #endif 사이에 있는 모든 코드를 평가합니다. #endif(#endif)는 #else 뒤에 있는 다음 전처리기 지시문이어야 합니다.

#endif#if 지시문으로 시작한 조건부 지시문의 끝을 지정합니다.

빌드 시스템은 SDK 스타일 프로젝트의 여러 대상 프레임워크를 나타내는 미리 정의된 전처리기 기호도 인식합니다. 둘 이상의 .NET 버전을 대상으로 지정할 수 있는 애플리케이션을 만들 때 유용합니다.

대상 프레임워크 기호 추가 기호
(.NET 5+ SDK에서 사용 가능)
플랫폼 기호(OS별
TFM을 지정할 때만 사용 가능)
.NET Framework
.NET Standard
.NET 5 이상(및 .NET Core)
[OS][version](예를 들어 IOS15_1),
[OS][version]_OR_GREATER(예를 들어 IOS15_1_OR_GREATER)

참고 항목

  • 버전 없는 기호는 대상으로 지정하는 버전과 무관하게 정의됩니다.
  • 버전별 기호는 대상으로 지정하는 버전에 대해서만 정의됩니다.
  • <framework>_OR_GREATER 기호는 대상으로 지정하는 버전과 모든 이전 버전에 대해 정의됩니다. 예를 들어 .NET Framework 2.0을 대상으로 지정하는 경우 NET20, NET20_OR_GREATER, NET11_OR_GREATER, NET10_OR_GREATER 기호가 정의됩니다.
  • NETSTANDARD<x>_<y>_OR_GREATER 기호는 .NET Standard 대상에 대해서만 정의되며 .NET Core 및 .NET Framework와 같은 .NET Standard를 구현하는 대상에는 정의되지 않습니다.
  • 이는 MSBuild TargetFramework 속성NuGet에서 사용되는 TFM(대상 프레임워크 모니커)와는 다릅니다.

참고 항목

기존의 비 SDK 스타일 프로젝트의 경우 프로젝트의 속성 페이지를 통해 Visual Studio의 여러 대상 프레임워크에 대한 조건부 컴파일 기호를 수동으로 구성해야 합니다.

다른 미리 정의된 기호에는 DEBUGTRACE 상수가 포함됩니다. #define을 사용하여 프로젝트에 설정된 값을 재정의할 수 있습니다. 예를 들어 DEBUG 기호는 빌드 구성 특성(“디버그” 또는 “릴리스” 모드)에 따라 자동으로 설정됩니다.

다음 예제에서는 파일에 MYTEST 기호를 정의한 다음, MYTESTDEBUG 기호의 값을 테스트하는 방법을 보여 줍니다. 이 예제의 출력은 디버그 또는 릴리스 구성 모드에서 프로젝트를 빌드했는지에 따라 다릅니다.

#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
    }
}

다음 예제에서는 가능한 경우 새 API를 사용할 수 있도록 여러 대상 프레임워크에 대해 테스트하는 방법을 보여 줍니다.

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

기호 정의

다음 두 전처리기 지시문을 사용하여 조건부 컴파일용 기호를 정의하거나 정의 취소합니다.

  • #define: 기호를 정의합니다.
  • #undef: 기호 정의를 취소합니다.

#define을 사용하여 기호를 정의합니다. 기호를 #if 지시문에 전달되는 식으로 사용하면 다음 예제와 같이 식이 true로 평가됩니다.

#define VERBOSE

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

참고 항목

C#에서는 기본 상수는 const 키워드를 사용하여 정의해야 합니다. const 선언은 런타임에 수정할 수 없는 static 멤버를 만듭니다. C 및 C++에서 일반적으로 수행하듯이 #define 지시문을 사용하여 상수 값을 선언할 수 없습니다. 이러한 상수가 여러 개 있는 경우 상수를 포함할 별도의 "Constants" 클래스를 만드는 것이 좋습니다.

기호를 사용하여 컴파일 조건을 지정할 수 있습니다. #if 또는 #elif(을)를 사용하여 기호를 테스트할 수 있습니다. ConditionalAttribute를 사용하여 조건부 컴파일을 수행할 수도 있습니다. 기호를 정의할 수 있지만 기호에 값을 할당할 수는 없습니다. #define 지시문은 파일에서 전처리기 지시문이 아닌 명령을 사용하기 전에 나와야 합니다. DefineConstants 컴파일러 옵션을 사용하여 기호를 정의할 수도 있습니다. #undef(으)로 기호 정의를 해제할 수 있습니다.

영역 정의

다음 두 전처리기 지시문을 사용하여 개요에서 축소할 수 있는 코드 영역을 정의할 수 있습니다.

  • #region: 영역을 시작합니다.
  • #endregion: 영역을 종료합니다.

#region을 사용하면 코드 편집기의 개요 기능을 사용할 때 확장하거나 축소할 수 있는 코드 블록을 지정할 수 있습니다. 더 긴 코드 파일에서 현재 작업 중인 파일 부분에 집중할 수 있도록 하나 이상의 영역을 편리하게 축소하거나 숨길 수 있습니다. 다음 예제에서는 영역을 정의하는 방법을 보여 줍니다.

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

#region 블록은 #endregion 지시문으로 종료해야 합니다. #region 블록은 #if 블록과 겹칠 수 없습니다. 그러나 #region 블록은 #if 블록에 중첩될 수 있고 #if 블록은 #region 블록에 중첩될 수 있습니다.

오류 및 경고 정보

다음 지시문을 사용하여 사용자 정의 컴파일러 오류 및 경고를 생성하고 줄 정보를 제어하도록 컴파일러에 지시합니다.

  • #error: 지정된 메시지를 사용하여 컴파일러 오류를 생성합니다.
  • #warning: 지정된 메시지를 사용하여 컴파일러 경고를 생성합니다.
  • #line: 컴파일러 메시지를 사용하여 출력된 줄 번호를 변경합니다.

#error를 사용하면 코드의 특정 위치에서 CS1029 사용자 정의 오류를 생성할 수 있습니다. 예시:

#error Deprecated code in this method.

참고 항목

컴파일러는 #error version을 특수한 방식으로 처리하며 사용된 컴파일러 및 언어 버전을 포함한 메시지가 있는 컴파일러 오류 CS8304를 보고합니다.

#warning을 사용하면 코드의 특정 위치에서 CS1030 수준 1 컴파일러 경고를 생성할 수 있습니다. 예시:

#warning Deprecated code in this method.

#line을 사용하면 오류 및 경고에 대한 컴파일러의 줄 번호 매기기 및 파일 이름 출력(선택 사항)을 수정할 수 있습니다.

다음 예제에서는 줄 번호와 관련된 두 개의 경고를 보고하는 방법을 보여 줍니다. #line 200 지시문은 다음 줄 번호를 강제로 200(기본값은 #6임)으로 설정하며, 다음 #line 지시문까지 파일 이름이 “Special”로 보고됩니다. #line default 지시문은 줄 번호 매기기를 기본 번호 매기기로 되돌립니다. 이 경우 이전 지시문을 통해 번호가 다시 매겨진 줄이 계산됩니다.

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;
    }
}

컴파일은 다음 출력을 생성합니다.

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

#line 지시문은 빌드 프로세스의 자동화된 중간 단계에서 사용할 수 있습니다. 예를 들어 원래 소스 코드 파일에서 줄이 제거되었지만 여전히 컴파일러에서 파일의 원래 줄 번호 매기기에 따라 출력을 생성하려는 경우, 줄을 제거한 다음 #line을 사용하여 원래 줄 번호 매기기를 시뮬레이트할 수 있습니다.

#line hidden 지시문은 개발자가 코드를 단계별로 실행할 때 #line hidden과 다음 #line 지시문(다른 #line hidden 지시문이 아니라고 가정) 사이에 있는 모든 줄이 프로시저 단위로 실행되도록 디버거에서 연속되는 줄을 숨깁니다. 이 옵션을 사용하여 ASP.NET이 사용자 정의 코드와 컴퓨터에서 생성된 코드를 구분하도록 할 수도 있습니다. ASP.NET이 해당 기능의 주 소비자지만 기능을 사용할 소스 생성기가 늘어날 가능성이 큽니다.

#line hidden 지시문은 오류 보고의 파일 이름이나 줄 번호에는 영향을 주지 않습니다. 즉, 컴파일러는 숨겨진 블록에서 오류를 찾는 경우 오류의 현재 파일 이름과 줄 번호를 보고합니다.

#line filename 지시문은 컴파일러 출력에 표시하려는 파일 이름을 지정합니다. 기본적으로 소스 코드 파일의 실제 이름이 사용됩니다. 파일 이름은 큰따옴표(“ ”)로 묶어야 하고 줄 번호 뒤에 와야 합니다.

C# 10부터 #line 지시문의 새로운 형식을 사용할 수 있습니다.

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

이 형식의 구성 요소는 다음과 같습니다.

  • (1, 1): 시작 행과, 지시문 직후에 오는 행의 첫 번째 문자의 열입니다. 이 예제에서 다음 행은 1행, 1열로 보고됩니다.
  • (5, 60): 표시된 영역의 끝 줄과 열입니다.
  • 10: #line 지시문을 적용할 열 오프셋입니다. 이 예제에서는 10열이 1열로 보고됩니다. 바로 이곳에서부터 선언 int b = 0;이 시작됩니다. 이 필드는 선택적입니다. 생략하면 지시문이 첫 번째 열부터 적용됩니다.
  • "partial-class.cs" 출력 파일의 이름입니다.

위 예제에서는 다음 경고를 생성합니다.

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

다시 매핑한 후 b 변수는 파일 partial-class.cs의 첫 번째 줄 6번째 문자에 있습니다.

DSL(Domain-Specific Language)은 일반적으로 이 형식을 사용하여 소스 파일과 생성된 C# 출력의 더 나은 매핑을 제공합니다. 이 확장 #line 지시문의 가장 일반적인 용도는 생성된 파일에 표시되는 경고 또는 오류를 원래 소스에 다시 매핑하는 것입니다. 예를 들어 다음 Razor 페이지를 고려합니다.

@page "/"
Time: @DateTime.NowAndThen

속성 DateTime.Now(이)가 DateTime.NowAndThen(으)로 잘못 입력되었습니다. 이 Razor 코드 조각에 대해 생성된 C#은 page.g.cs에서 다음과 같이 보입니다.

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

이전 코드 조각의 컴파일러 출력은 다음과 같습니다.

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

page.razor의 줄 2, 열 6은 텍스트 @DateTime.NowAndThen(이)가 시작되는 위치입니다. 이는 지시문에 (2, 6)(으)로 표시됩니다. @DateTime.NowAndThen의 범위는 줄 2, 열 27에서 끝납니다. 이는 지시문에 (2, 27)(으)로 표시됩니다. DateTime.NowAndThen 텍스트는 page.g.cs의 열 15에서 시작합니다. 이는 지시문에 15(으)로 표시됩니다. 모든 인수를 함께 배치하면 컴파일러는 page.razor에서 해당 위치에 오류를 보고합니다. 개발자는 생성된 소스가 아닌 소스 코드의 오류로 직접 이동할 수 있습니다.

이 형식에 대한 추가 예제를 보려면 기능 사양의 예제 섹션을 참조하세요.

Pragma

#pragma는 이 코드가 표시되는 파일의 컴파일에 대한 특수 명령을 컴파일러에 제공합니다. 컴파일러에서 명령을 지원해야 합니다. 즉, #pragma를 사용하여 사용자 지정 전처리 명령을 만들 수 없습니다.

#pragma pragma-name pragma-arguments

여기서 pragma-name은 인식된 pragma의 이름이고 pragma-arguments는 pragma 관련 인수입니다.

#pragma warning

#pragma warning은 특정 경고를 사용하거나 사용하지 않도록 설정합니다.

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

여기서 warning-list는 쉼표로 구분된 경고 번호 목록입니다. "CS" 접두사는 선택 사항입니다. 경고 번호를 지정하지 않은 경우 disable은 모든 경고를 사용하지 않도록 설정하고 restore는 모든 경고를 사용하도록 설정합니다.

참고 항목

Visual Studio에서 경고 번호를 찾으려면 프로젝트를 빌드하고 출력 창에서 경고 번호를 찾습니다.

disable은 소스 파일의 다음 줄부터 적용됩니다. 경고는 restore 다음 줄에서 복원됩니다. 파일에 restore가 없는 경우 경고는 동일한 컴파일에 있는 이후 파일의 첫 번째 줄에서 기본 상태로 복원됩니다.

// 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()
    {
    }
}

#pragma checksum

ASP.NET 페이지 디버깅을 돕기 위해 소스 파일에 대한 체크섬을 생성합니다.

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

여기서 "filename"은 변경 내용이나 업데이트를 모니터링해야 하는 파일의 이름이고 "{guid}"는 해시 알고리즘의 GUID(Globally Unique Identifier)이고 "checksum_bytes"는 체크섬의 바이트를 나타내는 16진수 문자열입니다. 짝수의 16진수여야 합니다. 홀수를 사용하면 컴파일 시간 경고가 발생하고 지시문이 무시됩니다.

Visual Studio 디버거는 체크섬을 사용하여 항상 올바른 소스를 찾도록 합니다. 컴파일러는 소스 파일에 대한 체크섬을 계산한 다음 출력을 PDB(프로그램 데이터베이스) 파일로 내보냅니다. 그런 다음 디버거는 PDB를 사용하여 소스 파일에 대해 계산하는 체크섬과 비교합니다.

체크섬이 .aspx 파일이 아니라 생성된 소스 파일에 대해 계산되므로 이 솔루션은 ASP.NET 프로젝트에서 작동하지 않습니다. 이 문제를 해결하기 위해 #pragma checksum은 ASP.NET 페이지에 대한 체크섬 지원을 제공합니다.

Visual C#에서 ASP.NET 프로젝트를 만드는 경우 생성된 소스 파일에 소스가 생성되는 .aspx 파일에 대한 체크섬이 포함됩니다. 그런 다음 컴파일러는 PDB 파일에 이 정보를 씁니다.

컴파일러가 파일에서 #pragma checksum 지시문을 찾지 못하면 체크섬을 계산하고 PDB 파일에 값을 씁니다.

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