NGMsoftware

NGMsoftware
로그인 회원가입
  • 매뉴얼
  • 학습
  • 매뉴얼

    학습


    C# 23-2. C#의 특성 (Attributes)

    페이지 정보

    본문

    안녕하세요. 소심비형입니다. 오늘 새로운 사업과 관련해서 동업을 희망하는 사람을 만나보았습니다. 처음 만나는 자리라서 구체적인 결과물을 낼 수는 없었지만, 여러가지 관심있게 들을만한 것들은 얻었던거 같네요. 제 아이디어를 모두 노출하는 위험 부담을 안고 만났기 때문에 긍정적인 결과를 기대했지만... 아무것도 가진게 없는 저로써는 그분들에게 전혀 도움이 되지 않는 존재일뿐이었습니다.

     

    사실 비전문가들에게 비즈니스 모델을 보여주면서 긍정적인 반응에 잠깐 고무된 적도 있으나, 역시 많이 해본 분들이 바라보는 시각은 지금까지와는 전혀 다른 방식으로 접근하고 분석한다는 것을 알게 되었습니다.

    3LO4zdG.png

     

     

    좀 더 현실적인 방향으로 계획을 수정하고, 천천히 혼자 개발해봐야 겠네요. 프로토타입 정도는 만들고 다시 처음부터 알아봐야 겠습니다. 개발을 할 수 있다는 큰 자산을 가지고 있으면서 잠시 한눈을 판게 애초부터 잘못된게 아닌가 생각되네요. 돈이 하나도 없다보니 뭘 하나 진행하기도 쉽지 않군요-_-;

     

    오늘은 C#의 특성에 대해 나머지 이야기들을 풀어나가겠습니다. 아마도... 당분간은 홈페이지를 관리할 수 없을거 같네요. 사업 아이템에 대한 앱을 만들면서 간단한 팁정도를 적을 수 있을거 같습니다^^

     

    C#은 그 자체만으로도 막강한 기능을 빠르게 개발할 수 있다는 장점을 가지고 있습니다. 하지만, 이런 수많은 장점에도 불구하고 더 생산성이 좋은 방법들이 존재하고 있습니다. 물론, 이런 방법을 도입하기 위해 지불해야 하는 비용도 계산해봐야 겠지만 말이죠^^; 남들이 잘 사용하지 않거나 레퍼런스가 부족한 신기술을 도입하는 것은 협업에서 크게 환영받지 못할뿐더러 당장은 쉽게 구현이 가능하지만, 유지보수 측면에서 더 많은 비용을 지출할수도 있습니다. 이 이슈는 프레임워크를 도입하는 하나의 이유이기도 합니다.

    아래 링크된 글은 외부 라이브러리의 함수를 이용하여 원하는 결과를 만들어 내는 방법에 대해 언급되어 있습니다.

    23. Natural sorting이 되는 GridControl 만들기. ]

     

    위 내용은 예제로 사용하기에 내용이 어려우므로 간단하게 Natural Sorting이 되는 방법에 대해 알아보겠습니다. 우선, 왜 이런 방법을 사용하는지 이야기하자면 기본적으로 .NET에서 제공하는 정렬 기능은 복합적인 정렬에 사용할 수 없기 때문이라고 말할 수 있습니다. 다시말해서 숫자와 문자가 혼합되어 있을 때 또는 숫자타입을 사용할 수 없을 때 자연스럽게 정렬되지 않는 문제를 가지고 있습니다. 까다롭지 않은 고객이라면 이런 부분에 대해서 크게 신경쓰지 않겠지만, 좀 더 자연스러운 정렬을 구현하기 위해서는 IComparer를 구현해야 합니다.

     

    Console 프로젝트를 하나 추가하세요. Program의 코드를 아래와 같이 변경합니다.

    Program.cs

    using System;
    using System.Collections.Generic;
    namespace NeturalSorting
    {
        class Program
        {
            static void Main(string[] args)
            {
                var sortList = new List<string>(); 
                
                for (int i = 1; i < 16; i++)
                {
                    sortList.Add(i.ToString());
                }
    
                sortList.Sort(); 
                
                foreach (var item in sortList)
                {
                    Console.WriteLine(item);
                }
    
                Console.ReadKey();
            }
        }
    }

     

     

    17라인에서 컬렉션에 들어 있는 요소들을 정렬 해줍니다. 만약, 제네릭이 int였다면 아무런 문제도 발생하지 않습니다. 하지만, string으로 되어 있기 때문에 생각처럼 정렬이 되지 않습니다. 아래 결과를 보면 알 수 있죠.

    uyyALPD.png

     

     

    그렇다면, 정수형으로 처리하면 되지 않느냐라고 물어볼 수 있을거 같습니다^^; 하지만 늘 그렇듯이 쉽게 해결되면 재미가 없자나요? 그렇습니다. 숫자와 문자가 복합적으로 사용될 때도 자연스럽게 정렬되기를 원하기 때문입니다. 꼭 숫자만 사용할 수 없는 상황이 올 때 난감하죠. 그러면, 자연스럽게 정렬이 되도록 직접 구현해야 하죠. IComparer를 구현해서요. 구현하는 방법은 여러가지가 존재합니다. Linq를 이용할수도 있고 IComparer 인터페이스의 Compare맴버를 구현하는 것입니다. 아무튼 복잡합니다-_-; 그런데 생각해보니 윈도우는 벌써 자연스럽게 정렬이 되고 있다는 것을 알 수 있습니다. 숫자와 문자가 조합된 폴더들을 만들고 정렬해보면 쉽게 알 수 있죠.

    DiHGSrG.png

     

     

    그럼 이런 기능들을 c#에서 이용하려면 어떻게 해야 할까요? 바로 DllImport 특성을 이용하여 외부 라이브러리의 함수를 호출해주면 됩니다. 그럼 이제 쉽게 자연스러운 정렬을 구핸해 보겠습니다. 아래와 같이 네임스페이스를 추가 해주세요.

    Program.cs

    using System;using System.Collections.Generic;using System.Runtime.InteropServices;

     

     

    extern 키워드를 사용하여 DllImport 특성에 부여된 외부 라이브러리의 함수를 불러옵니다. 이를 Unmanaged Code라고 부릅니다. .NET에서 관리되지 않기 때문이죠. extern은 외부 라이브러리의 함수라는 의미입니다.

    Program.cs

    using System;
    using System.Collections.Generic;
    using System.Runtime.InteropServices;
    namespace NeturalSorting { class Program 
    {        
        /// <summary>        
        /// 두 유니 코드 문자열을 비교합니다. 문자열의 숫자는 숫자 내용이 아닌 텍스트로 간주됩니다. 이 테스트는 대소 문자를 구분하지 않습니다.        
        /// </summary>        
        /// <param name="psz1">첫 번째 Null로 끝나는 문자열에 대한 포인터를 비교합니다.</param>        
        /// <param name="psz2">두 번째 Null로 끝나는 문자열에 대한 포인터를 비교합니다.</param>        
        /// <returns>문자열이 동일한 경우 0을 반환합니다.        
        /// psz1이 psz2보다 크면 1을 반환합니다.        
        /// psz1이 psz2보다 작으면 -1을 반환합니다.</returns>        
        [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]        
        internal static extern int StrCmpLogicalW(string psz1, string psz2);

     

     

    위와 같이 외부 함수를 정의합니다. 이 함수를 사용하는 방법은 매우 쉽습니다-_-; 아래 전체 코드를 통해 테스트 해볼 수 있습니다.

    Program.cs

    using System;
    using System.Collections.Generic;
    using System.Runtime.InteropServices;
    namespace NeturalSorting
    {
        class Program
        {
            /// <summary>        
            /// 두 유니 코드 문자열을 비교합니다. 문자열의 숫자는 숫자 내용이 아닌 텍스트로 간주됩니다. 이 테스트는 대소 문자를 구분하지 않습니다.        
            /// </summary>        
            /// <param name="psz1">첫 번째 Null로 끝나는 문자열에 대한 포인터를 비교합니다.</param>        
            /// <param name="psz2">두 번째 Null로 끝나는 문자열에 대한 포인터를 비교합니다.</param>        
            /// <returns>문자열이 동일한 경우 0을 반환합니다.        
            /// psz1이 psz2보다 크면 1을 반환합니다.        
            /// psz1이 psz2보다 작으면 -1을 반환합니다.</returns>        
            [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)] 
            internal static extern int StrCmpLogicalW(string psz1, string psz2); 
            
            static void Main(string[] args)
            {
                var sortList = new List<string>(); 
                
                for (int i = 1; i < 16; i++)
                {
                    sortList.Add(i.ToString() + " A");
                }
    
                sortList.Sort(StrCmpLogicalW); 
                
                foreach (var item in sortList)
                {
                    Console.WriteLine(item);
                }
    
                Console.ReadKey();
            }
        }
    }

     

     

    정수와 문자가 혼합되었을 때 제대로 정렬이 되는지 확인하기 위해 26라인에 문자 "A"를 추가했습니다. 그리고, 제네릭에 Expression으로 함수를 할당합니다. 이외에도 IComparer도 인자로 받을 수 있기 때문에 인터페이스를 구현한 파생 클래스가 와도 정상 동작합니다. 이제 확인하기 위해 실행(F5)해보세요.

    CHwT03C.png

     

     

    이외에도 수많은 특성이 있고, C#만으로는 구현하기 힘든 여러가지 일들을 할 수 있습니다. NG Framework를 종료할 때 메시지박스에 카운트가 되는 것도 외부 함수를 통해 구현되어 있습니다. .NET에서 제공하고 있는 MessageBox로는 구현이 불가능하기 때문이죠.

     

    마지막으로 사용자 특성에 대해 알아보겠습니다. 아래 코드는 MS-PL 라이센스를 가진 오픈 예제 코드입니다. 그리 복잡하지 않은 코드라서 직접 만드는 것보다는 참고해서 응용하는게 더 좋을거 같아서 가져다 붙입니다-_-; 사실 오픈 예제를 사용해서 부족한 설명 부분에 코멘트를 추가하려 했으나 워낙 설명이 자세히 되어 있어서 뭔가 더 추가하기가 껄끄럽네요. 혹여 이해하는데 방해가 되지 않을까 해서 원본 소스를 그냥 올립니다.

    AttributesTutorial.cs

    // Copyright © Microsoft Corporation. 모든 권리 보유.
    // 이 코드는 다음 라이선스의 약관에 따라 릴리스되었습니다. 
    // Microsoft Public License(MS-PL, http://opensource.org/licenses/ms-pl.html)////Copyright (c) Microsoft Corporation. 모든 권리 보유.
    // AttributesTutorial.cs
    // 이 예제에서는 클래스 및 메서드의 특성을 사용하는 방법을 보여 줍니다.
    using System;
    using System.Reflection;
    using System.Collections;
    
    // IsTested 클래스는 사용자 정의 사용자 지정 특성 클래스입니다.
    // 이 클래스는 다음을 포함하여 모든 선언에 적용할 수 있습니다
    //  - 형식(구조체, 클래스, 열거형, 대리자)
    //  - 멤버(메서드, 필드, 이벤트, 속성, 인덱서)
    // 이 클래스는 인수를 사용하지 않습니다.
    public class IsTestedAttribute : Attribute
    {
        public override string ToString()
        {
            return "Is Tested";
        }
    }
    
    // AuthorAttribute 클래스는 사용자 정의 특성 클래스입니다.
    // 이 클래스는 클래스 및 구조체 선언에만 사용할 수 있습니다.
    // 이 클래스는 명명되지 않은 문자열 인수 한 개(작성자 이름)와
    // 형식이 int인 명명된 선택적 인수 Version을 한 개 사용합니다.
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
    public class AuthorAttribute : Attribute
    {
        // 이 생성자는 명명되지 않은 인수를 특성 클래스에 지정합니다.    
        public AuthorAttribute(string name)
        {
            this.name = name; this.version = 0;
        }
    
        // 이 속성은 set 접근자가 없는 읽기 전용 속성이므로    
        // 이 특성에 대해 명명된 인수로 사용할 수 없습니다.    
        public string Name
        {
            get
            {
                return name;
            }
        }
    
        // 이 속성은 set 접근자가 있는 읽기/쓰기 속성이므로    
        // 이 클래스를 특성 클래스로 사용하는 경우 해당 속성을    
        // 명명된 인수로 사용할 수 있습니다.    
        public int Version
        {
            get
            {
                return version;
            }
            set
            {
                version = value;
            }
        }
    
        public override string ToString()
        {
            string value = "Author : " + Name; if (version != 0)
            {
                value += " Version : " + Version.ToString();
            }
            return value;
        }
        private string name; private int version;
    }
    
    // 여기에서 사용자 정의된 AuthorAttribute 사용자 지정 특성을 Account
    // 클래스에 연결합니다. 특성을 만들 때는 명명되지 않은 문자열 인수가
    // AuthorAttribute 클래스 생성자에 전달됩니다.
    [Author("Joe Programmer")]
    class Account
    {
        // IsTestedAttribute 사용자 지정 특성을 이 메서드에 연결합니다.    
        [IsTested]
        public void AddOrder(Order orderToAdd)
        {
            orders.Add(orderToAdd);
        }
        private ArrayList orders = new ArrayList();
    }
    
    // AuthorAttribute 및 IsTestedAttribute 사용자 지정 특성을 이 클래스에 
    // 연결합니다.
    // AuthorAttribute에는 명명된 인수인 'Version'이 사용되었습니다.
    [Author("Jane Programmer", Version = 2), IsTested()]
    class Order{    
        // 참고할 내용이 있으면 여기에 추가합니다.
    }
    
    class MainClass
    {
        private static bool IsMemberTested(MemberInfo member)
        {
            foreach (object attribute in member.GetCustomAttributes(true))
            {
                if (attribute is IsTestedAttribute)
                {
                    return true;
                }
            }
            return false;
        }
        private static void DumpAttributes(MemberInfo member)
        {
            Console.WriteLine("Attributes for : " + member.Name); 
            
            foreach (object attribute in member.GetCustomAttributes(true))
            {
                Console.WriteLine(attribute);
            }
        }
        public static void Main()
        {
            // Account 클래스의 특성을 표시합니다.        
            DumpAttributes(typeof(Account));
            // 테스트한 멤버 목록을 표시합니다.        
            foreach (MethodInfo method in (typeof(Account)).GetMethods())
            {
                if (IsMemberTested(method))
                {
                    Console.WriteLine("Member {0} is tested!", method.Name);
                }
                else
                {
                    Console.WriteLine("Member {0} is NOT tested!", method.Name);
                }
            }
            Console.WriteLine();
            // Order 클래스의 특성을 표시합니다.        DumpAttributes(typeof(Order));        
            // Order 클래스의 메서드에 대한 특성을 표시합니다.       
            foreach (MethodInfo method in (typeof(Order)).GetMethods())
            {
                if (IsMemberTested(method))
                {
                    Console.WriteLine("Member {0} is tested!", method.Name);
                }
                else
                {
                    Console.WriteLine("Member {0} is NOT tested!", method.Name);
                }
            }
    
            Console.WriteLine(); Console.ReadKey();
        }
    }

     

     

    실행하면 아래와 같은 결과를 확인할 수 있습니다.

    G9rLIO9.png

     

     

    아직 리플렉션에 대해 학습하지 않았기에 이 코드를 100% 이해하는데 어려움이 있을수도 있습니다. 이 예제는 사용자 지정 특성 클래스를 만들어 코드에서 사용하고 리플렉션을 통해 쿼리하는 방법을 보여 줍니다. 좀 복잡해 보일지도 모르지만, 코드를 실행하고 단계를 따라가다보면 어떻게 동작하는지 좀 더 쉽게 이해할 수 있을겁니다. 시간이 되면 좀 더 간단한 예제를 추가해보려 했지만 귀찮아서...

     

    결국, 특성이라는 것은 직접 사용할수는 없고 리플렉션을 통해 해당 속성 또는 맴버의 데이타를 제공하는 역할을 합니다. 물론, 메소드 내에 구현되어 있는 어떤 기능을 동작하기도 하죠. 이렇게 타입에서 특성을 분석해보면 이 클래스나 메소드가 특별한 무언가를 수행할 수 있도록 구현할 수도 있습니다. 아직 알아보지는 않았지만 특성을 통해 속성에 제약 조건을 걸어줄 수도 있습니다. 실제로 EF에서는 사용되고 있는 기술입니다.

     

    다음 시간에...

    • 네이버 공유하기
    • 페이스북 공유하기
    • 트위터 공유하기
    • 카카오스토리 공유하기
    추천0 비추천0

    댓글목록

    등록된 댓글이 없습니다.