C 및 C++ 지시문과 달리, 매크로를 만드는 데는 해당 지시문을 사용할 수 없다. 전처리기 지시문은 한 줄에서 유일한 명령이어야 한다.
Null 허용 컨텍스트
#nullable 전처리기 지시문은 null 허용 주석이 적용되는지와 null 허용 여부 경고가 지정되는지를 제어한다. 각 컨텍스트는 disabled 또는 enabled이다. 주석 및 경고 컨텍스트를 제어하고 프로젝트 수준 설정보다 우선으로 적용된다. 다른 지시문이 재정의할 때까지 제어하는 컨텍스트를 설정하거나 소스 파일의 끝까지 설정한다.
지시문의 효과는 다음과 같다.
- #nullable disable: null 허용 주석 및 경고 컨텍스트를 disabled로 설정한다.
- #nullable enable: null 허용 주석 및 경고 컨텍스트를 enabled로 설정한다.
- #nullable restore: null 허용 주석 및 경고 컨텍스트를 프로젝트 설정으로 복원한다.
- #nullable disable annotations: null 허용 주석 컨텍스트를 disabled로 설정한다.
- #nullable enable annotations: null 허용 주석 컨텍스트를 enabled로 설정한다.
- #nullable restore annotations: null 허용 주석 컨텍스트를 프로젝트 설정으로 복원한다.
- #nullable disable warnings: null 허용 경고 컨텍스트를 disabled로 설정한다.
- #nullable enable warnings: null 허용 경고 컨텍스트를 enabled로 설정한다.
- #nullable restore warnings: null 허용 경고 컨텍스트를 프로젝트 설정으로 복원한다.
조건부 컴파일
네 가지 전처리기 지시문을 사용하여 조건부 컴파일을 제어한다.
- #if: 지정된 기호가 정의된 경우에만 코드가 컴파일되는 조건부 컴파일을 연다.
- #elif: 앞에 있는 조건부 컴파일을 닫고 지정된 기호가 정의되었는지에 따라 새 조건부 컴파일을 연다.
- #else: 앞에 있는 조건부 컴파일을 닫고 이전 지정된 기호가 정의되지 않은 경우 새 조건부 컴파일을 연다.
- #endif: 앞에 있는 조건부 컴파일을 닫는다.
컴파일러는 #if 지시문을 찾은 후 마지막으로 #endif 지시문을 찾으면 지정된 기호가 정의된 경우에만 지시문 사이에 있는 코드를 컴파일한다. C 및 C++와 달리, 기호에 숫자 값을 할당할 수 없다. C#의 #if 문은 부울이고, 기호가 정의되었는지 여부만 테스트한다.
#if DEBUG
Console.WriteLine("Debug version");
#endif
==(같음) 및 !=(같지 않음) 연산자를 사용하여 bool 값 true 또는 false를 테스트할 수 있다. true가 반환되면 기호가 정의된 것이다. #if DEBUG 문의 의미는 #if (DEBUG == true)와 같다. &&(and), ||(or) 및 !(not) 연산자를 사용하여 여러 기호가 정의되었는지를 평가할 수 있다. 기호와 연산자를 괄호로 묶을 수도 있다. #if를 #else, #elif, #endif, #define 및 #undef 지시문과 함께 사용하면 하나 이상의 기호 유무에 따라 코드를 포함하거나 제거할 수 있다. 조건부 컴파일은 코드를 디버그 빌드용으로 컴파일하거나 특정 구성용으로 컴파일할 때 유용할 수 있다.
#if 지시문으로 시작되는 조건부 지시문은 #endif 지시문을 사용하여 명시적으로 종료해야 한다. #define 또는 DefineConstants 컴파일러 옵션을 사용하여 기호를 정의할 수도 있다. #undef로 기호 정의를 해제할 수 있다. #define을 사용하여 만든 기호의 범위는 해당 기호가 정의된 파일이다. DefineConstants 또는 #define으로 정의하는 기호는 동일한 이름의 변수와 충돌하지 않는다. 즉, 변수 이름이 전처리기 지시문에 전달되지 않아야 하며 전처리기 지시문을 통해서만 기호를 평가할 수 있다.
#elif 식은 앞에 있는 #if 및 모든 앞에 있는 선택적 #elif 지시문 식이 true로 평가되지 않는 경우 평가된다. #elif 식이 true로 평가되면 컴파일러는 #elif와 다음 조건부 지시문 사이에 있는 모든 코드를 평가한다.
#define VC7
//...
#if debug
Console.WriteLine("Debug build");
#elif VC7
Console.WriteLine("Visual Studio 7");
#endif
앞에 있는 #if 또는 (선택적) #elif 지시문의 식이 true로 평가되지 않으면 컴파일러는 #else와 다음 #endif 사이에 있는 모든 코드를 평가한다. #endif(#endif)는 #else 뒤에 있는 다음 전처리기 지시문이어야 한다.
#endif는 #if 지시문으로 시작한 조건부 지시문의 끝을 지정한다. 빌드 시스템은 SDK 스타일 프로젝트의 여러 대상 프레임워크를 나타내는 미리 정의된 전처리기 기호도 인식한다. 둘 이상의 .NET 버전을 대상으로 지정할 수 있는 애플리케이션을 만들 때 유용하다.
- 버전 없는 기호는 대상으로 지정하는 버전과 무관하게 정의된다.
- 버전별 기호는 대상으로 지정하는 버전에 대해서만 정의된다.
- <framework>_OR_GREATER 기호는 대상으로 지정하는 버전과 모든 이전 버전에 대해 정의된다. 예를 들어 .NET Framework 2.0을 대상으로 경우 NET20, NET20_OR_GREATER, NET11_OR_GREATER, NET10_OR_GREATER 기호가 정의된다.
- MSBuild TargetFramework 속성 및 NuGet에서 사용하는 TFM(대상 프레임워크 모니커)과는 다르다.
기존의 비 SDK 스타일 프로젝트의 경우 프로젝트의 속성 페이지를 통해 Visual Studio의 여러 대상 프레임워크에 대한 조건부 컴파일 기호를 수동으로 구성해야 한다. 다른 미리 정의된 기호에는 DEBUG 및 TRACE 상수가 포함된다. #define을 사용하여 프로젝트에 설정된 값을 재정의할 수 있다. 예를 들어 DEBUG 기호는 빌드 구성 특성(“디버그” 또는 “릴리스” 모드)에 따라 자동으로 설정된다.
다음 예제에서는 파일에 MYTEST 기호를 정의한 다음, MYTEST및 DEBUG 기호의 값을 테스트하는 방법을 보여 준다. 이 예제의 출력은 디버그 또는 릴리스 구성 모드에서 프로젝트를 빌드했는지에 따라 다르다.
#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로 평가된다. 또한 using 토큰 이전에 정의를 할 수가 없다.
#define VERBOSE
#if VERBOSE
Console.WriteLine("Verbose output version");
#endif
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 filename 지시문은 컴파일러 출력에 표시하려는 파일 이름을 지정한다. 기본적으로 소스 코드 파일의 실제 이름이 사용된다. 파일 이름은 큰따옴표(“ ”)로 묶어야 하고 줄 번호 뒤에 와야 한다.
C# 10부터 #line 지시문의 새로운 형식을 사용할 수 있다.
#line (1, 1) - (5, 60) 10 "partial-class.g.cs"
/*34567*/int b = 0;
이 형식의 구성 요소는 다음과 같다.
- (1, 1): 지시문 뒤에 있는 줄의 첫 번째 문자에 대한 시작선 및 열이다. 이 예제에서 다음 행은 1행, 1열로 보고된다.
- (5, 60): 표시된 영역의 끝 줄과 열이다.
- 10: #line 지시문을 적용할 열 오프셋이다. 이 예제에서는 10열이 1열로 보고된다. 바로 이곳에서부터 선언 int b = 0;이 시작된다. 이 필드는 선택 항목이다. 생략하면 지시문이 첫 번째 열부터 적용된다.
- "partial-class.g.cs" 출력 파일의 이름이다.
위 예제에서는 다음 경고를 생성한다.
partial-class.g.cs(1,5,1,6): warning CS0219: The variable 'b' is assigned but its value is never used
다시 매핑이 이루어지면 변수 b가 첫 번째 행 여섯 번째 문자에 있다. DSL(Domain-Specific Language)은 일반적으로 이 형식을 사용하여 소스 파일과 생성된 C# 출력의 더 나은 매핑을 제공한다.
Pragma
이 코드가 표시되는 파일의 컴파일에 대한 특수 명령을 컴파일러에 제공한다. 컴파일러에서 명령을 지원해야 한다. 즉, #pragma를 사용하여 사용자 지정 전처리 명령을 만들 수 없다.
- #pragma warning: 경고를 사용하거나 사용하지 않도록 설정한다.
- #pragma checksum: 체크섬을 생성한다.
#pragma pragma-name pragma-arguments
여기서 pragma-name은 인식된 pragma의 이름이고 pragma-arguments는 pragma 관련 인수다.
#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
}
}
출처 : https://docs.microsoft.com/ko-kr/dotnet/csharp/language-reference/preprocessor-directives
'프로그래밍 언어 > C#' 카테고리의 다른 글
C# 확장 메서드 (Extension Method) (0) | 2022.07.18 |
---|---|
C# 리플렉션과 어트리뷰트 (Reflection and Attributes) (0) | 2022.07.08 |
C# Action/Func/Predicate (0) | 2022.07.04 |
C# 배열 복사 방법 (0) | 2022.07.01 |
C# 얕은 복사 깊은 복사 (0) | 2022.07.01 |