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에서의 규칙

  1. 생성자 인자: [Author("Anders", 1)] 처럼 순서대로 위치 기반으로 값을 넘김
  2. 명명된 인자 (Version = 1): 반드시 속성(property) 또는 필드(field) 중에서 public이고 set 가능한 것만 허용됨🔥 왜 Version = 1이 생성자 인자랑 동시에 못 쓰이는가?즉,
  3. 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)] ❌ 오류

 

 

 

+ Recent posts