ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • C# 커스텀 어트리뷰트 전처리문
    .NET/C# Basic 2008. 10. 17. 04:46
    반응형



    /////////// 커스텀어트리뷰트 ////////////////

    사용자가 직접 정의하는 어트리뷰트를 커스텀어트리뷰트라고 한다. 컴파일 방식이나 생성되는 기계어 코드에는 전혀 영향을 주지 않으며 실행 파일에 메타 데이터로 포함될 뿐이다 코드에 설명을 다는 일종의 주석이라고 할 수 있는데 자유롭게 붙이는 문자열 형태의 주석과는 달리 일정한 형식이 있으며 프로그래밍 방식으로 조작할 수 있으므로 자동화된 처리가 가능하다는 점이 다르다

    커스텀 어트리뷰트를 어떻게 정의해서 어디다 사용할 것인가느 그야말로 사용자 마음대로이다 컴파일러는 어트리뷰트의 형식을 체크하여 제대로 작성했는지 점검하고메타 데이터로 기록만 해놓을뿐 이 데이터를 읽어서 사용하는 주체는 사용자이다 다음 예제는 메서드와 필드에 제작자와 작성시기를 어트리뷰트로 기록한다 어틀뷰트만 붙였으므로 실행해봐야 별 출력은 없다


    using System;

    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field, AllowMultiple = true, Inherited = false)]

    class ProgrammerAttribute : Attribute
    {
     private string Name;
     private string Time;
     public ProgrammerAttribute(string aName)
     {
      Name = aName;
      Time = "기록없음";
     }
     public string When
     {
      get { return Time; }
      set { Time = value; }

     }
    }

    class CStest
    {
     [Programmer("Kim")]
     static public int Field1 = 0;

     [Programmer("Kim",When= "2007년 6월 29일")]
     static public void Method1(){}

     [Programmer ("Lee")]
     static public void Method2(){}
     
     [Programmer("Park"),Programmer("Choi")]
     static public void Method3(){}

     static void Main()
     {
     
     }
    }


    어트리뷰트도 클래스로 정의하는데 위 예제는 ProgrammerAttribute라는 이름으로 커스텀 어트리뷰트를 하나 정의한다 커스텀 어트리뷰트는 Attribute 클래스로부터 상속받아야 하며 강제 사항은 아니지만 클래스 이름은 ~ Attribute 로 끝나는 것이 좋다. 꼬랑지가 Attribute로 끝나면 이 어트리뷰트를 지정할 떄는 접미를 생략할 수 있어 편리하다  즉 ProgrammerAttrubute는 이름 전부를 쓸 수도 있고 간단하게 Programmer로 쓸 수도 있다

    어트리뷰트 클래스도 다른 클래스와 마찬가지로 생성자 프로퍼티 필드 메서드 등의 맴버를 가질수 있다 Programmer 에는 제작자 이름을 기억하는  Name 필드와 작성시점을 기록하는 TIme필드맴버를 선언했다 그리고 생성자와  Time 필드를 읽고 쓰는 When 이라는 프로퍼티를 가진다 생성자는 제작자 이름을 받아 Name  필드를 초기화하고 Time은 기록없음 으로 디폴트 초기화한다 Time을 변경할 때는 When = "언제" 식으로 대입하면 된다

    어트리뷰트를 적용할 떄는 [] 괄호 안에 어트리뷰트를 명시하고 () 괄호 안에 인수를 전달하여 초기화한다 어트리뷰트의 인수는 이름 여부에 따라 두 가지 종류로 구분되는데 초기화하는 방법이 다르다. 이름없는 인수는 생성자에서 실인수값을 받아 초기화하며 이름 있는 인수는 필드나 프로퍼티로 표현하므로 어트리뷰트 지정식에 A=B 식으로 이름을 지정하여 대입한다
     Programmer 에서 Name 이 이름 없는 인수이며 When 은 이름있는 인수이다 . 이름없는 인수는 생성자로 전달되므로 반드시 지정해야 하지만 이름있는 인수는 필요할 떄만 지정할 수 있다 둘 다 지정할 경우  이름 없는 인수가 이름 있는 인수보다 반드시 먼저 와야 한다. 이름 있는 인수가 둘 이상 있을 떄 순서는 중요하지 않으므로 편한대로 지정하면 된다. 다음 두 지정문의 효과는 동일하다

    [SomeAttr("unnamed",A=1,B=2)]
    [SomeAttr("unnamed",B=2,A=1)]


    인수에 대입되는 값에는 아무 타입이나 쓸 수 없고 약간의 제약이 있다 기본형과 string , object , Type 클래스 열거형 그리고 1차원 배열만 가능하다 2차원 배열이나 클래스 타입의 객체는 인수에 대입될 수 없다 그래서 Programmer 어트리뷰트에서 시점을 표현하는 Time이 DateTime같은 객체가 아니라 단순한 string이다

    어트리뷰트 선언문에 또다른 어트리뷰트인 AttributeUsage가 지정되어있다 AttributeUsage는 어트리뷰트의 여러가지 성질을 지정하는데 필요없으면 생략할 수 있지만 대개의 경우는 정확한 대상에 대해 지정해야 하므로 꼭 필요하다. 어트리뷰트를 정의하는데 어트리뷰트가 사용되는 셈이다  AttributeUsage어트리뷰트로 다음 세 가지 성질을 지정한다.

     ValidOn  : 이름 없는 인수이며 적용 가능한 요소값을 지정한다 . 어떤 어트리뷰트는 클래스에만 적용할 수 있고 어떤 어트리뷰트는 메서드에만 의미가 있을 수 있는데 이런 적용 대상을 지정한다 초기화할때 생성자로 딱 한번만 지정할 수 있으며 초기화되면 ValidOn 프로퍼티로 읽을수만 있다 AttributeTargets 열거형으로 어떤 코드에 이 어트리뷰트를 적용할 수 있는지를 지정하며 두 개 이상의 요소를 | 연산자로 묶어서 지정할수 있다
    AllowMutiple : 이름있는 인수이므로 필요할 떄만 지정하면 된다. 한대상에 여러 번 적용할 수 있는지를 지정하는데 디폴트는 false 이므로 딱한번만 지정할 수 잇다 동일한 어트리뷰트를 여러번지정하는 것이 의미가 있다면 이 인수를 true로 변경해야한다
    Inherited : 이름있는 인수이므로 필요할 떄만 지정하면 된다 파생되는 클래스나 재정의되는 메서드에도 적용할 것인가를 지정하는데 디폴트는 true 이다 . 특정 클래스에 이 어트리뷰트를 지정하면 파생되는 모든 클래스에도 적용된다

    Programmer  어트리뷰트는 메서드와 필드에 대해서만 적용하도록 초기화 했다 . 따라서 이 어트리뷰트를 클래스나 프로퍼티에 적용하면 에러 처리된다 AllowMultiple 은  true로 초기화하여 여러번 적용할 수 있도록 했는데 한 메서드를 여러명의 개발자가 같이 만들 수도 있으므로 중복 적용 가능해야한다  Inherited 는 false로 초기화하여 상속은 하지 않았다

    CSTest  에서 한의 필드와 세개의 메서드를 정의했는데 각 요소에 대해  Programmer  어트리뷰트를 붙여 누가 언제 만든것인지를 기록해 두었다 Field1 은 Kim 이 만들었으며 Method1 은 Kim이 2007년 6월 29일 만들었다. Method2 는 Lee가 만들었고 Method3 는 Park Choi 가  같이 만들었을을 어트리뷰트로 기록해 두었다 어트리뷰트를 중복 지정할 떄는 순서에 상관없이 지정할 수 있으며 쉼표로 끊어서 지정할수 있고 [] 괄호를 따로 쓸수도 있다

    [return:Some]
    public static int Method1(string s) {return 0;}


    Some이라는 어트리뷰트를 Method1에 적용했다 그런데 만약 메서드 전체가 아닌 리턴 타입에만 적용하고 싶다면 다음과같이 지정문 앞에 return : 붙인다. 이런 식으로 assembly :, module : , type : , method :, param: 등의 적용 대상 지정문이 있는 어트리뷰트는 지정한 요소에만 적용된다

    어트리뷰트는 단순히 기록을 남기는 주석일 뿐이므로 컴파일 결과에는 아무런 영향을 미치지 않느다 그러나 실행파일에 메타데이터로 포함되므로 리플렉션으로 이정보를 관리할 수 잇다 예를 들어 위 예제의 경우 어떤 메서드에서 버그가 발생했다면 누가 언제 추가한 메서드인지 알아낼 수 있으며 프로젝트 관리를 별도의 프로그램으로 만들어 자동화활 수 있다
    대규모의 팀 프로젝트를 원활하게 수행하기 위해서는 개발 외에 치밀한 관리도 아주 중요하다 프로젝트를 관리하는 작업을 위해 별도의 프로젝트 관리 툴도 흔히 사용하는데 그만큼 프로젝트 관리가 만만치 않다는 뜻이다 어트리뷰트를 잘활용하면 프로젝트 관리에 필요한 모든 정보를 프로젝트 자체에 내장할 수 있다. 작성자와 시점 외에도 수정사항, 우선순위 버그 목록, 메모등을 이트리뷰트로 일일이 기록해 놓으면 이정보를 여러가지 용도로 활용할 수 있으며 프로젝트 관리 업무가 대폭 자동화된다


    //////////// 전처리문 //////////////

    전처리문은 C++의 전처리기와 유사하며 C++의 형식을 빌려 만든 문법이라고 할수 있다 기계어 코드로 바뀌는 것은 아니지만 컴파일과 소스를 관리하는 방식에 영향을 미친다. C++의 전처리는 명령에 따라 소스를 재구성하는 과정을 의미하는데 C# 에서는 실제로 전처리 과정이 존재하지 않으며 컴파일러가 처리한다
    하지만 관습상 이런 명령을 전처리명령이라고 불러왔기 떄문에 C#도 전처리라는 용어를 계속 사용한다 C#의 전처리 명령들은 종류와 형식이 C++ 과 거의 비슷하고 동작이나 용도도 같다. 단 헤더 파일을 인클루드 하는 # include는  없는데 C#은 선언순서를 주용시 여기지 않는 언어이며 헤더 파일이라는 것이 없기 떄문이다 C#의 전처리 명령들을 간단하게 정리해 보자

    #define, #undef

    심볼을 정의하거나 취소하며 뒤쪽에 정의하고자 하는 심볼을 지정한다 #define SYMBOL 식으로 사용하며 단순히 존재를 정의할 뿐이지 어떤 값을 대입하는 것은 아니다 C++에서 칭하는 매크로 상수를 정의하는 것이다. 이렇게 정의된 심볼은 조건부 컴파일 지시자와 함께 사용된다 반드시 소스의 선두에 적어야 하며 심지어  using선언보다 더 앞에 있어야 한다. 프로젝트 속성창에서도 심볼을 정의할 수 있다

    #if #else #elif #endif

    조건부 컴파일 지시자이다 심볼의 존재여부에 따라 특정부분을 컴파일하거나 제외한다 대상코드를#if와 #endif 로 감싸면 이부분이 조건부 컴파일된다. 심볼이 없는 경우를 처리하고 싶다면 중간에 #else를 쓸 수도 있고 또 다른 조건을 지정하고 싶다면 #elif를 쓸 수도 있다

     이 두 전처리문을 사용하면 조건부 컴파일을 할 수 있다 앞에서 Conditional   어트리뷰트로 만들었던 예제를 전처리문으로 다시 작성해보자


    #define TRIAL

    using System;

    class CStest
    {

    //#if TRAIL
     static void TrialMessage()
     {
    #if TRAIL                         -- 어트리뷰트로는 할수없는것
      Console.WriteLine("이 제품은 30일 간만 사용할 수 있습니다");
      Console.WriteLine("정품을 구입하시려면 ssogarif@nate.com");
    #endif                            -- 어트리뷰트로는 할수없는것
      Console.WriteLine("?");
     }
    //#endif


     static void Main()
     {
    #if TRIAL
      TrialMessage();
    #endif
      Console.WriteLine("게임을 시작합니다");
     }
    }

    TRIAL 심볼을 정의하고 이 심볼의 정의 여부에 따라 Trial Message메서드 정의문과 호출문을 조건부로 컴파일하도록 했다. 전처리문은 들여쓰기를 하지 않기 떄문에 소스모양이 좋지 못하며 호출문까지 일일이 #if ~ #endif로 감싸야 하므로 불편하기도 하다 이런 불편함 떄문에 C#은 conditional  어트리뷰트를 제공하는 것이다
    그러나 이 방식이 불편하다 해도 아직까지는 쓸데가 많으며 어트리뷰트보다 훨씬 더 융통성이 있다. 워낙 단순한 방식으로 동작하기 떄문에 메서드뿐만 아니라 개별 코드 하나하나를 조건부 처리할 수 있고 클래스나 필드에 대해서도 조건부 처리할 수 있다. 위의 소스와 같이 메서드 내의 일부 코드에 대한 조건부 컴파일은 어트리뷰트로는 할 수 없다.

    또한 && , || 같은 논리 연산자를 쓸 수 있어 두개의 조건을 논리합이나 논리곱으로 연결하기도 쉽다. Conditional 어트르뷰트는 여러가지 조건을 적용하기가 굉장히 불편하며 게다가 #else 에 해당하는 기능이 없어 선택적으로 코드를 컴파일할 수 없다. 떄로는 논리적으로 동작하는 신형보다 기계적으로 동작하는 구형이 더 편리한 경우도 있다

    #warning, #error

    뒤쪽의 문자열을 경고나 에러로 출력한다. 경고는 출력 창에 메시지만 출력하고 컴파일은 계속 되지만 에러는 메시지 출력 후 컴파일을 즉시 중지한다 특정한 조건이 되지 않을떄 이를 개발자에게 알리거나 조건이 만족될 떄까지 컴파일을 거부하고 싶을 때 이 전처리문을 사용한다. 예를 들어 라이브러리 버전이 너무 낮다거나 문자 인코딩 방식이 맞지 않을 떄 컴파일 해 봐야 동작하지 않는다는 것을 분명히 알릴 필요가 있다

    #region , #endregion

    코드의 블록에 이름을 주고 하나의 단위로 표시한다 컴파일과는 아무 상관이 없으며 편집기가 이부분을 잠시 감출 수 있도록 영역지정만 해놓는것이다 자주편집하지 않는 코드를 이영역으로 감싸 놓으면 축소해 놓을 수 있어 소스편집에 편리하다. 다음 그림은 앞에 만들었던 DllImport 예제의 외부 DLL 정의문을 영역으로 감싸놓은면 유용하다. 외부DLL을 많이 사용할 경우 수십개나 선언해야 하는데 이부분은 한번만 선언하면 더이상 손델 필요가 없다. 게다가 선언문이 워낙 복잡해서 보기만 해도 정신이 사나운데 이런부분을 영역으로 지정해 놓으면 전체가 하나의 단위가 되며 옆에[-]버튼이 나타난다. 이버튼을 누르면 선언문 전체가 축소되어 시원스럽게 보이며 순에 거슬리지 않는다 축소된 영역은 흐린색으로 리전의 이름만 보이는데 펼쳐보고싶을때는 언제든지 [+]버튼을 누르기만 하면 된다. 이 전처리문은 사용자가 직접 쓸 수도 있지만 컴파일러가 소스 관리 차원에서 더 많이 사용한다. 보통 사용자가 직접 작성하지 않는 코드를 이런 영역에 배치하여 신경 쓰지 않아도 되게끔 한다

    #line

    파일 이름과 줄 번호를 변경한다 이번호는 경고나 에러를 출력할 떄 위치를 가리키기 위해 사용된다 일반적인 목적으로는 사용되지 않으며 디버깅이나  로그를 남길때 가끔 사용된다 #line default는 원래 줄 번호로 돌아간다





    반응형

    댓글

Designed by Tistory.