5.1.1 구문
5.1.1.1 전처리기 지시문
C#의 전처리기 지시문(preprocessor directive)은 특정 소스코드를 상황에 따라 컴파일 과정에서 추가/제거하고 싶을 때 사용한다.
소스코드 파일이 나뉘면 경우에 따라 여러 문제가 발생한다. 하나의 소스코드에서 변경된 사항을 다른 소스코드에 반영해야하는 경우 관리상의 부담이 생긴다.
메서드를 통해 중복 코드 방지를 한 것처럼 소스코드 파일 역시 중복되는 것은 지양해야한다.
이럴 때 #if/#endif 전처리기 지시문을 사용할 수 있다.

그림 5.2 프로젝트 속성 창의 빌드/조건부 컴파일 기호 설정
솔루션 파일 속성 - Build - Debut 사용자 심볼을 추가한다.
class Program
{
static void Main(String[] args)
{
string txt = Console.ReadLine();
if (string.IsNullOrEmpty(txt) == false)
{
Console.WriteLine("사용자 입력: " + txt);
}
#if OUTPUT_LOG
else
{
Console.WriteLine("입력되지 않음");
}
#endif
}
}

#if, #endif가 조건문이기에 이를 보완하기 위한 #else, #elif 지시자도 있다.
전처리기 기호를 dotnet build의 옵션으로 지정하는 방법 외에 소스코드에서 #define문으로 직접 정의하고 #undef 문으로 정의를 취소할 수 있다.
#define __X86__
#undef OUTPUT_LOG
#if OUTPUT_LOG
Console.WriteLine("OUTPUT_LOG 정의");
#else
Console.WriteLine("OUTPUT_LOG 정의 안됨");
#endif
#if __X86__
Console.WriteLine("__X86__정의");
#elif __X64__
Console.WriteLine("__X64__정의");
#endif
#출력
OUTPUT_LOG 정의 안됨
__X86__ 정의
** #define/#undef 구문은 반드시 소스코드 보다 먼저 나타나야 한다. (맨 상단, Using문 보다 먼저 나타나야함)
그 외 #warning, #error, #line, #region, #endregion, #pragma 지시문이 있다.
5.1.1.2 지역 변수의 유효 범위
주석은 소스코드 파일에만 존재할 뿐, 컴파일러에 의해 빌드된 후 생성되는 EXE/DLL 파일에는 남지 않는다.
이 문제를 해결 할 방법은 특성(attribute)이다.
닷넷의 어셈블리 파일에는 해당 어셈블리 스스로를 기술하는 메타데이터가 포함돼 있다.
어셈블리 내에서 구현하고 있는 타입, 그 타입 내에 구현된 멤버등의 정보가 메타데이터에 해당된다.
특성(attribute)은 이러한 메타데이터에 함께 포함되며, 원하는 데이터를 보관하는 특성을 자유롭게 정의해서 사용 가능하다.
attribute 자체도 클래스이다. [Flags] 특성은 FlagsAttribute 클래스로서 ms에서 미리 만들어 BCL에 포함해둔 것이다.
사용자 정의 특성(Attribute)
특성은 System.Attribute를 상속받았다는 점을 제외하고는 여느 클래스와 차이점이 없다. 관례상 특성 클래스의 이름에는 Attribute라는 접미사를 붙인다.
[AuthorAttribute]
class Dummy1
{
}
[Author]
class Dummy2 // C#에서는 Attribute 접미사를 생략해도 된다.
{
}
[Author()] // 마치 new Author() 처럼 생성자를 표현하는 듯한 구문도 사용 가능하다.
class Dummy3
{
}
class AuthorAttribute : System.Attribute
{
}
특성 클래스에 매개변수가 포함된 생성자를 추가 할 수 있다.
예제5.1 Author 특성
[Author("Author")] // new Author("Author"); 와 같은 사용 구문을 연상하면 된다.
class Program
{
static void Main(string[] args)
{
}
}
class AuthorAttribute : System.Attribute
{
string name;
public AuthorAttribute(string name)
{
this.name = name;
}
}
선택적으로 값을 지정하고 싶다면 attribute 클래스의 속성으로 정의하는 편이 좋다.
[Author("Author", Version =1)] // new Author("Author"); 와 같은 사용 구문을 연상하면 된다.
class Program
{
static void Main(string[] args)
{
}
}
class AuthorAttribute : System.Attribute
{
string name;
int version;
public int Version
{
get { return version; }
set { version = value; }
}
public AuthorAttribute(string name)
{
this.name = name;
}
}
attribute는 프로그램의 흐름에 직접적인 영향을 끼치지 않으면서 개발자로 하여금 정보를 남길 수 있는 기능을 제공한다.
*특성 자체는 프로그램의 동작 방식에 관여할 수 없다. 다만 Reflection 기술과 결합되면 응용 범위가 확장된다.
특성이 적용될 대상을 제한
[Flags()]
class Program
{
}

Flags 특성은 이 선언 형식에는 사용할 수 없다.
Flags 특성은 enum 타입의 동작 방식을 바꾸는 용도로 사용되기에 class 정의에 사용될 이유가 없다.
닷넷에서는 특성의 용도를 제한할 목적으로 System.AttributeUsageAttribute라는 또 다른 특성을 제공한다.
AttributeUsage 특성에는 enum 타입의 AttributeTargets 값을 인자로 받는 생성자가 정의 돼 있는데, 이 AttributeTargets에 정의된 값을 보면 특성이 적용될 수 있는 대상을 확인할 수 있다.
표 5.1 특성을 적용할 수 있는 대상 목록
AttributeTargets 값 | 의미 |
Assembly | 어셈블리가 대상인 특성 |
Module | 모듈이 대상인 특성 |
Class | class가 대상인 특성 |
Struct | struct가 대상인 특성 |
Enum | enum이 대상인 특성 |
Constructor | 타입의 생성자가 대상인 특성 |
Method | 타입의 메서드가 대상인 특성 |
Property | 타입의 속성이 대상인 특성 |
Field | 타입의 필드가 대상인 특성 |
Event | 타입의 이벤트가 대상인 특성 |
Interface | interface가 대상인 특성 |
Parameter | 메서드의 매개변수가 대상인 특성 |
Delegate | delegate가 대상인 특성 |
ReturnValue | 메서드의 반환값에 지정되는 특성 |
GenericParameter | C# 2.0에 추가된 제네릭 매개변수에 지정되는 특성 |
All | AttributeTargets에 정의된 모든 대상을 포함 |
메서드 내부의 코드를 제외한 C#의 모든 소스코드에 특성 부여가 가능하다.
특성을 정의할 때 AttributeUsage를 지정하지 않으면 기본값으로 AttributeTargets.All이 지정된것과 같다.
Author 특성은 표 5.1에 지정된 모든 대상에 적용 가능하다.
Author 특성의 적용 대상을 클래스와 메서드로 제한하고 싶다면 다음과 같이 사용가능하다.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
class AuthorAttribute : System.Attribute
{
}
특성을 사용하는 대괄호 구문에는 특성이 적용될 대상(target)을 명시하는 것이 가능하다.
using System;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
class AuthorAttribute : Attribute
{
public string Name { get; }
public AuthorAttribute(string name)
{
Name = name;
}
}
[type: Author("Tester")]
class Program
{
[method: Author("Tester")]
static void Main(string[] args)
{
}
}
AttributeTargets 의 값에 따라 대괄호 안의 대상이 달라지는데, 다음의 표는 이러한 관계를 보여준다.
표 5.2 AttributeTargets와 대상 지정
AttributeTargets 값 | [target: ....] |
Assembly | assembly |
Module | module |
Class | type |
Struct | type |
Enum | type |
Constructor | method |
Method | method |
Property | property |
Field | field |
Event | event |
Interface | type |
Parameter | param |
Delegate | type |
ReturnValue | return |
GenericParameter | typevar |
일반적으로 대상을 생략 하면 특성이 명시된 코드의 유형에 따라 표 5.2의 대상이 자동으로 선택된다.
하지만 경우에 따라 반드시 명시해야하는 특성이 있다.
BCL의 MarshalAs라는 특성은 적용 대상이 Field, Parameter, ReturnValue로 돼 있다. 이 중 MarshalAs를 ReturnValue 대상으로 적용하는 경우는 다음과 같다.
using System.Runtime.InteropServices;
[MarshalAs(UnmanagedType.I4)]
static int Main(string[] args)
{
return 0;
}

MarshalAs 특성은 field, param, return 선언에만 사용 할 수 있다는 에러가 나온다. 특성이 적용된 코드가 Main 메서드이기 떄문에 자동으로 [method: MarshalAs(...)]로 지정되고, method 는 MarshalAs의 대상인 Field, Parameter, ReturnValue에 속하지 않기 때문이다.
using System.Runtime.InteropServices;
[return: MarshalAs(UnmanagedType.I4)]
static int Main(string[] args)
{
return 0;
}
명시적으로 return 값에 적용된다는 의미로 대상을 설정하면 컴파일 에러가 발생되지 않는다.
다중 적용과 상속
AttributeUsage 특성에는 생성자로 입력받는 AttributeTargets 말고 두가지 속성이 더 있다.
표 5.3 AttributeTargets의 두가지 속성
속성 타입 | 속성 이름 | 의미 |
bool | AllowMultiple | 대상에 동일한 특성이 다중으로 정의 가능(기본값: false) |
bool | Inherited | 특성이 지정된 대상을 상속받는 타입도 자동으로 부모의 특성을 물려받음. 일반적으로 잘 사용되지 않는다.(기본값 true) |
AllowMultiple, Inherited 속성은 AttributeUsage 클래스의 생성자에 매개변수로 정의돼 있지 않고 속성으로만 정의돼 있기 때문에 특성을 적용하는 대괄호 구문에서 '이름 = 값'의 쌍으로 전달한다.
예제 5.1의 Author 특성을 다음과 같이 다중으로 지정하면 "특성이 중복되었습니다." 라는 컴파일 에러가 발생한다.
class AuthorAttribute : Attribute
{
public string Name { get; }
public int Version { get; set; }
public AuthorAttribute(string name)
{
this.Name = name;
}
}
[Author("Anders", Version = 1)]
[Author("Brad", Version = 2)]
class Program
{
}

동일한 특성을 두개 이상 지정 하려면 Author 클래스의 AttributeUsage 설정에 AllowMultiple 속성을 true로 설정한다.
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
class AuthorAttribute : Attribute
{
public string Name { get; }
public int Version { get; set; }
public AuthorAttribute(string name)
{
this.Name = name;
}
}
[Author("Anders", Version = 1)]
[Author("Brad", Version = 2)]
class Program
{
}
같은 특성이 아니라면 AllowMultiple 여부에 상관없이 대상 코드에 여러 개의 특성을 지정하는 것이 가능하다.
enum 타입에 Flags와 Author 특성을 지정하면 다음과 같다.
class AuthorAttribute : Attribute
{
public string Name { get; }
public int Version { get; set; }
public AuthorAttribute(string name)
{
this.Name = name;
}
}
[Flags]
[Author("Anders")]
enum Days { Monday, }
class Program
{
}
또는 대괄호 내에 연속해서 정의하는 것도 가능하다.
class AuthorAttribute : Attribute
{
public string Name { get; }
public int Version { get; set; }
public AuthorAttribute(string name)
{
this.Name = name;
}
}
[Flags, Author("Anders")]
enum Days { Monday, }
class Program
{
}
**참고
class AuthorAttribute : Attribute
{
public string Name { get; }
public int Version { get; set; } // ← set 가능한 속성
public AuthorAttribute(string name)
{
Name = name;
}
}
...
[Author("Anders", Version = 1)]
class abc
{
...
}
"왜 AuthorAttribute(string name, int version) 생성자 쓰면 Version = 1 에러 나요?"
이유는 간단합니다:
🔍 C# Attribute에서의 규칙
- 생성자 인자: [Author("Anders", 1)] 처럼 순서대로 위치 기반으로 값을 넘김
- 명명된 인자 (Version = 1): 반드시 속성(property) 또는 필드(field) 중에서 public이고 set 가능한 것만 허용됨🔥 왜 Version = 1이 생성자 인자랑 동시에 못 쓰이는가?즉,
- C#에서는 Version = 1은 속성에 값을 설정하는 것이지, 생성자의 인자로 넘기는 게 아니에요.
public AuthorAttribute(string name, int version) { ... }
을 정의한 경우엔 다음처럼 버전도 생성자 인자로 넣어야 해요:
[Author("Anders", 1)] // ✅ OK, 생성자 인자 2개
하지만 이렇게 쓰면:
[Author("Anders", Version = 1)] // ❌ 컴파일 에러
이건 Version이라는 속성에 값을 주려고 시도하는 거지, 생성자의 두 번째 인자에 값을 넣으려는 게 아니기 때문에 C#이 혼동하지 않도록 금지되어 있어요.
✅ 결론: 해결 방법 두 가지
방법 1️⃣ – Version은 속성으로 남기고 생성자에서 제거
public AuthorAttribute(string name)
{
Name = name;
}
public int Version { get; set; } // 명명된 인자용
[Author("Anders", Version = 1)] // ✅ OK
방법 2️⃣ – Version도 생성자 인자로만 받기
public AuthorAttribute(string name, int version)
{
Name = name;
Version = version;
}
public int Version { get; } // 읽기 전용도 OK
[Author("Anders", 1)] // ✅ OK
하지만 이 경우 [Author("Anders", Version = 1)]처럼 속성 스타일로는 못 씀 ❌
🎓 요약
생성자에서 받음 | [Author("A", 1)] | ✅ 가능 |
속성(set 가능한)으로 정의함 | [Author("A", Version = 1)] | ✅ 가능 |
생성자에서도 받고, 속성으로도 설정함 | [Author("A", Version = 1)] | ❌ 오류 |
'C#(.Net)' 카테고리의 다른 글
[시작하세요 C# 12 프로그래밍 ] #5 C# 1.0 완성하기 #3 (0) | 2025.03.23 |
---|---|
[시작하세요 C# 12 프로그래밍 ] #5 C# 1.0 완성하기 #2 (0) | 2025.03.22 |
[시작하세요 C# 12 프로그래밍 ] #4 CSharp 객체 지향 문법 3 (1) | 2025.03.19 |
Field(클래스 내부 멤버 변수) vs Property(속성) vs Attribute(특성) (0) | 2025.03.18 |
[시작하세요 C# 12 프로그래밍 ] #4 CSharp 객체 지향 문법 2 (0) | 2025.03.16 |