컴파일러에는 별도의 전처리기가 없지만, 이 단원에 설명된 지시문은 전처리기가 있는 것처럼 처리됩니다. 조건부 컴파일에서 지시문을 유용하게 사용할 수 있습니다. C 및 C++ 지시문과 달리, 매크로를 만드는 데는 해당 지시문을 사용할 수 없습니다. 전처리기 지시문은 한 줄에서 유일한 명령이어야 합니다.
C# 컴파일러는 #if 지시문을 찾은 후 마지막으로 #endif 지시문을 찾으면 지정된 기호가 정의된 경우에만 지시문 사이에 있는 코드를 컴파일합니다. C 및 C++와 달리, 기호에 숫자 값을 할당할 수 없습니다. C#의 #if 문은 부울이고, 기호가 정의되었는지 여부만 테스트합니다.
참고 https://docs.microsoft.com/ko-kr/dotnet/csharp/language-reference/preprocessor-directives
어떤 전처리기 기호가 정의가 되었는지 판별하여 조건에 맞는 코드를 실행한다.
#define __X86__
#undef OUTPUT_LOG
using System;
class Program{
static void Main(string[] args)
{
#if OUTPUT_LOG
Console.WriteLine("OUTPUT_LOG 정의됨");
#else
Console.WriteLine("OUTPUT_LOG 정의 안 됨");
#if __X86__
Console.WriteLine("__x86__ 정의됨");
#elif __X86__
Console.WriteLine("__x86__ 정의 안 됨");
#endif
}
}
닷넷의 어셈블리 파일레는 해당 어셈블리 스스로를 기술하는 메타데이터가 포함돼 있다. 예를 들어, 어셈블리 내에서 구현하고 있는 타입, 그 타입 내에 구현된 멤버 등의 정보가 메타데이터에 해당한다.
특성은 이런 메타데이터에 함께 호함되며, 원하는 데이터를 보관하는 특성을 자유롭게 정의해서 사용 할 수 있다.
class AuthorAttribute : System.Attribute{
string name;
public AuthorAttribute(string name){
this.name = name;
}
}
[Author("Anders")]
class Program{
~~~
}
정수 계열 타입의 산술 연산을 하거나, 서로 다은 정수 타입 간의 형변환을 하게 되면 표현 가능한 숫자의 범위를 넘어서는 경우가 존재(오버플로우, 언더플로우). 보통 이러한 문제는 개발자가 의도한 경우가 아니다.
따라서 C#으로 하여금 이런 문제가 생기면 오류를 발생시키라고 명시할 수 있는데, 이때 checked
예약어가 그와 같은 역할을 한다.
short c = 32767;
int n = 32768;
-----------------------------------------
checked
{
c++; // 예외 발생! (System.OverflowException 발생.)
}
------------------------------------------
unchecked
{
c++; // 예외 발생 X
}
/checked
옵션을 컴팡일러에 적용하면 된다.메서드를 정의할 때 몇 개의 인자를 받아야 할지 정할 수 없을 때가 있다. 이때 쓰는 것이 params
예약어이다.
static int Add(params int[] values){
int result =0;
for (int i=0; i < values.length; i++{
result += values[i];
}
return result;
}
static void Main(string[] args){
Console.WriteLine(Add(1,2,3,4,5));
Console.WriteLine(Add(2,3,4,5,6,7,8,9));
}
닷넷 호환 언어로 만들어진 managed code에서 C/C++ 같은 언어로 만들어진 unmaged code의 기능을 사용하는 수단으로 플랫폼 호출(P/Invoke: platform invocation)이 있다. extern 예약어는 PInvoke 호출을 정의하는 구문에 사용된다.
Extern 구문에는 3가지 정보가 필요하다.
using System.Runtime.InteropServices;
class Program{
[DllImport("user32.dll")]
static extern int MessageBeep(uint uType);
static int TestMethod(uint type)/// 비교를 위한 정적 메소드
{
return 0;
}
static void Main(string[] args){
MessageBeep(0);
}
}
DllImport
특성을 적용해야만 이용할 수 있다.C#의 독특한 특징 중 하나는 기존 네이티브 언어(C/C++)와의 호환성을 위한 기능이 추가됐다는 점이다. Win32 API를 직접 호출할 수 있는 extern 예약어도 그런 사례 중 하나다.
C/C++와의 호환성을 높이기 위해 존재하는 또 한 가지 사례는 바로 안전하지 않은 컨텍스트(unsafe context)에 대한 지원이다.
unsafe context란 말 그대로 안전하지 않는 코드를 포함한 영역을 의미하며, 안전하지 않은 코드란 역시 포인터(pointer)가 되겠다.
즉, C#은 C/C++의 포인터를 지원하며 unsafe 예약어는 포인터를 쓰는 코드를 포함하는 클래스나 그것의 멤버 또는 블록에 사용한다.
unsafe static void GetAddResult(int* p, int a, int b)
{
*p = a + b;
}
static void Main(string[] args)
{
int i;
unsafe
{
GetAddResult(&i, 5, 10);
}
Console.WriteLine(i);
}
이런 안전하지 않는 소스코드는 반드시 컴파일러 옵션으로 /unsafe
를 지정해야한다.
unsafe 문맥에서 포인터를 사용할 수 있는 곳은 스택에 데이터가 저장된 변수에 한해 적용된다. 즉, 지역 변수나 매서드의 매개변수 타입이 값 형식인 경우에만 포인터 연산자(*,&)를 사용할 수 있다.
fixed
라는 예약어를 도입했다. 이 예약어는 힙에 할당된 참조 형식의 인스턴스를 가비지 수집기가 움직이지 못하도록 고정시킴으로써 포인터가 가리키는 메모리를 유효하게 만드는 것이다.class Managed
{
public int cnt;
public string name;
}
class Program
{
unsafe static void Main(string[] args)
{
Managed inst = new Managed();
inst.cnt = 5;
inst.name = "text";
fixed(int* pValue = &inst.cnt)
{
*pValue = 6;
}
fixed(char* pChar = inst.Name.ToCharArray())
{
for(int i =0; i < inst.Name.Length; i++)
{
Console.WriteLine(*(pChar+i));
}
}
}
}
주의!
C#은 객체 인스턴스의 포인터를 가져오는 것을 허용하지 않는다. 대신 해당 객체가 가진 멤버 데이터가 값 형식이거나 값 형식의 배열인 경우에만 포인터 연산을 할 수 있다.
하지만 fixed되는 대상은 객체의 데이터를 포함한 객체가 된다. 따라서 프로그램 실행이 fixed 블록의 끝에 다다를 때까지는 GC가 해당 객체를 이동시킬 수 없다.
보통 fixed된 포인터는 관리 프로그램의 힙에 할당된 데이터를 관리되지 않은 프로그램에 넘기는 용도로 쓰인다.
값 형식은 스택에 할당되고 참조 형식은 힙에 할당된다. 그런데 값 형식임에도 그것이 배열로 선언되면 힙에 할당된다.
stackalloc
예약어는 값 형식의 배열을 힙이 아닌 스택에 할당하게 한다.