목록

Customize Attribute

사실 C#에서 enum문을 string 형식 으로 사용할 때 사용되는 부분이여서 어떤 문법인지 한번 정리하고 가자.

#정의

메타데이터에서 특성 정의를 빠르고 쉽게 식별할 수 있도록 하는 Attribute에서 직접 또는 간접적으로 파생되는 특성 클래스를 정의하여 자체 사용자 지정 특성을 만들 수 있습니다. 형식을 작성한 프로그래머의 이름을 형식에 태그로 지정한다고 가정합니다. 사용자 지정 Author 특성 클래스를 정의할 수 있습니다.


#사용법(Usage)

  • 클래스 정의

    [System.AttributeUsage(System.AttributeTargets.Class |  System.AttributeTargets.Struct)]  
    public class Author : System.Attribute  
    {  
        private string name;  
        public double version;  
    
        public Author(string name)  
        {  
            this.name = name;  
            version = 1.0;  
        }  
    }

    클래스 이름은 특성의 이름인 Author 이며 System.Attribute 상속 받아야 한다. 특정 특성에서만 유효하게 설정하려면 AttributeUsage 을 이용해서 사용.

  • 어떻게 사용?

    [Author("P. Ackerman", version = 1.1)]  
    class SampleClass  
    {  
        // P. Ackerman's code goes here...  
            // 셋팅한 내용을 추후에 확인해보고 싶거나 이를 이용하여 내용을 구현하는 경우에는
            // Type객체를 이용하여 Attribute의 내용을 읽어들일 수 있다.
    }

    신기하다..

  • 특성 설정

    AttributeUsage에는 사용자 지정 특성을 한 번 또는 여러번 사용 할 수 있도록 설정이 가능한데.

    [System.AttributeUsage(System.AttributeTargets.Class |  
                    System.AttributeTargets.Struct,  
                    AllowMultiple = true)  // multiuse attribute  
    ]  
    public class Author : System.Attribute

    이렇게 AllowMultiple = true 지정 시 아래와 같이 사용 가능하다.

    [Author("P. Ackerman", version = 1.1)]  
    [Author("R. Koch", version = 1.2)]  
    class SampleClass  
    {  
        // P. Ackerman's code goes here...  
        // R. Koch's code goes here...  
    }
  • 코드 요소에 연결된 특성 사용법

    특성은 외부 영향이 없으면 어떤 작업도 수행하지 않는다. 특성을 찾아 작업을 수행하려면 일반적으로 리플렉션 이필요 하다는데 리플렉션 관련은 나중에 포스팅하자. 일단, 이러한 특성부여한걸 코드에 사용하고 싶다면 어떻게 해야할까??

    => TypeInfo 객체를 이용 GetCustomAttributes 메서드를 활용.

    예시로 - 리플렉션을 사용해 클래스에 대한 정보를 가져올 수 있다.

    TypeInfo typeInfo = typeof(MyClass).GetTypeInfo();
    Console.WriteLine("The assembly qualified name of MyClass is " + typeInfo.AssemblyQualifiedName);

    다음과 같이 출력.

    The assembly qualified name of MyClass is ConsoleApplication.MyClass, attributes, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
  • 실 사용예시

    Atrribute 클래스 정의

    [System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Struct)]
    public class AuthorTest : System.Attribute
    {
        private string name;
        public double version;
    
        public string Name { get => name; set => name = value; }
    
        public AuthorTest(string name)
        {
            this.Name = name;
            version = 1.0;
        }
    }

    테스트로 Atrribute부여된 임시 클래스 생성

    [AuthorTest("Shin.YoungMyung")]
    class SampleClass
    {
    
    }

    Attribute 확인

    static void Main(string[] args)
    {
            SampleClass _asd = new SampleClass();
            Type _type = _asd.GetType();
            //현재는 AuthorTest만 부여했으므로 [0]에 AuthorTest만 있다, 만약 다중 부여 시 배열로 넘어온다.
            System.Object[] _customAttrs = _asd.GetType().GetCustomAttributes(true);
    }

    디버깅으로 확인

    이런식으로 필요 정보들을 심을 수 있다. 신기하닷


#주의

Attribute 개체가 지연되어 인스턴스화 되는 것에 유의해야 하는데,

GetCustomAttribute 또는 GetCustomAttributes 를 사용해야만 인스턴스화 되고, 매번 인스턴스화 되기도한다.

GetCustomAttributes 를 연속해서 2번 호출하면 2개의 다른 ObsoleteAttribute 인스턴스가 반환된다.

  • BCL(기본 클래스 라이브러리)의 공통 특성

    .NET Core 기본 클래스 라이브러리에 기본 제고오디는 몇 가지 유의할 특성

    [Obsolete] : 이 특성은 위 예제에서 사용되었으며 System 네임스페이스에 있습니다. 기본 코드를 변경하는 방법에 대해 선언적 설명서를 제공하는 것이 유용합니다. 메시지는 문자열의 형태로 제공될 수 있으며 다른 부울 매개 변수가 컴파일러 경고를 컴파일러 오류로 에스컬레이션하는 데 사용될 수 있습니다.

    [Conditional] : 이 특성은 System.Diagnostics 네임스페이스에 있습니다. 이 특성은 메서드(또는 특성 클래스)에 적용할 수 있습니다. 생성자에는 문자열을 전달해야 합니다. 해당 문자열이 #define 지시문과 일치하는 경우 해당 메서드의 호출(메서드 자체는 아님)이 C# 컴파일러에 의해 제거됩니다. 일반적으로 이 특성은 디버깅(진단) 목적으로 사용됩니다.

    [CallerMemberName] : 이 특성은 매개 변수에 사용될 수 있으며 System.Runtime.CompilerServices 네임스페이스에 있습니다. 다른 메서드를 호출하는 메서드의 이름을 삽입하는 데 사용되는 특성입니다. 일반적으로 다양한 UI 프레임워크에서 INotifyPropertyChanged를 구현하는 경우 ‘매직 문자열’을 제거하는 방법으로 사용됩니다. - 관련 예제는 아래 링크에 넣어놨다.


#씨샵에서의 활용

enum문을 string 형식 으로 사용 할 수 있는 예제를 작성해보겠다.

  • enum문 필드에 부여할 Attribute 클래스 정의

    /// <summary>
    /// This attribute is used to represent a string value
    /// for a value in an enum.
    /// </summary>
    public class StringValueAttribute : Attribute {
    
            #region Properties
    
            /// <summary>
            /// Holds the stringvalue for a value in an enum.
            /// </summary>
            public string StringValue { get; protected set; }
    
            #endregion
    
            #region Constructor
    
            /// <summary>
            /// Constructor used to init a StringValue Attribute
            /// </summary>
            /// <param name="value"></param>
            public StringValueAttribute(string value) {
                    this.StringValue = value;
            }
            #endregion
    }
  • 속성부여한 필드값을 retrun하는 함수 정의

    /// <summary>
    /// Will get the string value for a given enums value, this will
    /// only work if you assign the StringValue attribute to
    /// the items in your enum.
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    public static string GetStringValue(this Enum value) {
            // enum 타입반환
            Type type = value.GetType();
    
            // 해당 enum 타입의 value값 필드 정보 넣기
            FieldInfo fieldInfo = type.GetField(value.ToString());
    
            // 해당 필드정보의 커스텀속성 찾기.
            StringValueAttribute[] attribs = fieldInfo.GetCustomAttributes(
                    typeof(StringValueAttribute), false) as StringValueAttribute[];
    
            // Return the first if there was a match.
            return attribs.Length > 0 ? attribs[0].StringValue : null;
    }
  • 실 코드 사용

    // enum 정의
    public enum TestEnum : int {
            [StringValue("a")]	//이부분이 string으로 쓸 부분 정의
            develop = 1,
            [StringValue("b")]
            staging = 2        
        }
    
    static void Main(string[] args)
    {
            TestEnum tetEnumValue = TestEnum.develop;
            string whatString = help.Funcs.GetStringValue(tetEnumValue);
            // "a"가 반환 됨을 알 수 있다.
    }

#참고