Search

Welcome to Dynamic C#(21) - 인덱스의 힘.

C# 2010.05.21 09:00 Posted by 뎡바1

- 인덱스는 왜 나오는 고냐...?

인덱스는 방대한 정보를 특정한 기준으로 잘 분류해 놔서 정보를 금방 찾을 수 있도록 해주는 고마운 장치이죠. 아무리 두꺼운 사전이 있다고 해도, 그 사전이 가나다 순이나 알파벳순으로 잘 인덱싱이 되어있지 않으면 쓸모없겠죠. C#도 그래서, 대화하기 여러운 COM과 잘 지내기 위해서 인덱스 가능한 프로퍼티를 사용하기 시작했쬬!


- 인덱스 가능한 프로퍼티가 몬데?

이름 그대로입니다. 그냥 이름 그대로 프로퍼티인데, 프로퍼티의 값을 가져올 때 인덱스로 특정 요소에 접근가능한 프로퍼티죠. 아래와 같은 모양이 인덱스로 접근하는 프로퍼티의 모습입니다.

myObject.MyIndexedProperty[index];

인덱스 가능한 프로퍼티는 내부적으로 그냥 간단한 접근자 메서드와 일반적으로 쓰이는 인덱서와 유사한 인덱서로 구성됩니다. 그냥 일반적으로 쓰는 인덱서처럼 생각할 수도 있죠. 그러면 일반적인 인덱서와의 차이점을 예제를 통해서 한번 확인해 보겠습니다. 우선 일반적인 인덱서의 예제를 보시죠.

class D
{
    private int[] nums = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 };

    public int this[int x]
    {
        get
        {
            return nums[x];
        }
    }
}

class C
{
    static void Main()
    {
        D d = new D();
        for (int i = 0; i < 8; i++)
        {
            Console.WriteLine(d[i]);
        }
    }
}


그리고 인덱스 가능한 프로퍼티의 사용예제입니다.

using System;
using Excel = Microsoft.Office.Interop.Excel;
using System.Collections.Generic;

namespace OfficeInteropExam2
{
    public class Account
    {
        public int ID { get; set; }
        public double Balance { get; set; }       
    }

    class Program
    {
        static void DisplayInExcel(IEnumerable<Account> accounts)
        {
            var excelApp = new Excel.Application();
            excelApp.Visible = true;
                       
            excelApp.Workbooks.Add();

            Excel._Worksheet workSheet = (Excel.Worksheet)excelApp.ActiveSheet;

            workSheet.Cells[1, "A"] = "ID Number";
            workSheet.Cells[1, "B"] = "Current Balance";

            var row = 1;
            foreach (var acct in accounts)
            {
                row++;
                workSheet.Cells[row, "A"] = acct.ID;
                workSheet.Cells[row, "B"] = acct.Balance;
            }

            workSheet.Range["A1", "B3"].AutoFormat(
                Excel.XlRangeAutoFormat.xlRangeAutoFormatClassic2);
        }

        static void Main(string[] args)
        {
            var bankAccounts = new List<Account> {
                new Account {
                  ID = 345678,
                  Balance = 541.27
                },
                new Account {
                  ID = 1230221,
                  Balance = -127.44
                }
            };

            DisplayInExcel(bankAccounts);
        }
    }
}


전자는 간단하게 인덱서를 사용하는 예제이구요, 두번째는 엑셀에 데이터를 표시하는 COM 프로그래밍 예제입니다. 두번째 예제에서 밑줄친 부분이 바로 인덱스 가능한 프로퍼티를 사용한 부분입니다. 겉으로 보기에는 별 차이가 없어 보이는데요. 내부적으로는 어떨까요? 리플렉터로 우선 첫번째 예제를 보겠습니다.


위에서 붉은 선으로 표시된 대로, 일반적인 인덱서 앞에는 'Item'이라는 이름이 붙습니다. 그리고 이 인덱서를 사용하는 부분의 코드는,

private static void Main()
{
    D d = new D();
    for (int i = 0; i < 8; i++)
    {
        Console.WriteLine(d[i]);
    }
}

위와 같이 소스코드와 큰 변화가 없는 걸 볼 수 있습니다. 그러면 두번째 예제는 어떨까요? 인덱스 가능한 프로퍼티가 사용된 부분만 리플렉터로 보면,

workSheet.get_Range("A1", "B3").AutoFormat(XlRangeAutoFormat.xlRangeAutoFormatClassic2, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value);

'Range["A1", "B3"]'이라고 썼던 부분이 'get_Range'라는 메서드 호출로 바뀐걸 보실 수 있습니다. 그러면 저 get_Range를 한번 찾아볼까요?


위에서 보시듯이 'Item'이라는 표시가 없습니다. 즉 일반적인 인덱서가 아니라는 이야기죠. 그리고 접근자 메서드를 가지고 있는 걸 보실 수 있습니다. 위에서 인덱스 가능한 프로퍼티가 '접근자 메서드와 일반적으로 쓰이는 인덱서와 유사한 인덱서로 구성'된다고 이야기 한 부분이 바로 이걸 가리키는 말이었던 거죠.


- 찝찝하지만..

인덱스 가능한 프로퍼티를 그동안 추가하지 않았던 이유는 C#팀의 디자인 철학과 맞지 않아서 였다고 합니다. C#팀에서는 'C라는 타입안에 있는 P라는 프로퍼티에 접근해서 값을 가지고 온다. 그리고 그 결과값에 대해서 인덱스로 접근한다'는 시나리오가 더 옳다고 생각한다고 하네요.

하지만, 인덱스 가능한 프로퍼티를 사용하는 COM이 무척이나 많이 퍼져있다는 게 문제였다고 합니다. 그래서 이전 포스트에서 언급 해드렸던 이유와 같은 이유로 COM 프로그래밍을 지원하기 위해서 이런 기능이 추가되었다고 하네요.

비록 인덱스 가능한 프로퍼티를 사용할 수 있긴 하지만, C#에서는 인덱스 가능한 프로퍼티를 선언할 수는 없습니다. COM 라이브러리가 tlbimp라는 툴을 이용해서 만들어 지기 때문에, C#에서는 만들수 없다고 하네요. 즉 C#에서는 COM 라이브러리에 대해서만 인덱스 가능한 프로퍼티를 사용할 수 있는 것이죠.

컴파일러는 COM 타입에서 인덱스 가능한 프로퍼티로 보이는 걸 모두 임포트해둔다고 합니다. 그래서 해당 프로퍼티에 대해서는 이름을 붙인 인덱서 문법을 이용해서 사용할 수 있도록 한다고 하네요. 즉, '컴파일러는 이름을 붙인 인덱서 문법을 통해서 인덱스 가능한 프로퍼티를 사용할 수 있도록 한다'는 것이죠. 위의 예제에서 보여드렸듯이 일반적인 프로퍼티는 접근자 메서드를 통해서 바로 접근할 수 없습니다. 하지만 문법적으로는 매우 유사한 형태를 취하면서 내부적으로는 다른 일이 벌어지고 있는 것이죠.


- 오버로드 판별도 해보자.

인덱스 가능한 프로퍼티가 추가되면서 또 오버로드 판별에 변수가 생겼습니다. 아래의 예제를 가지고 설명을 해보면요,

myObject.MyIndexedProperty[index];

우선 컴파일러가 이 구문을 보게 되면, '.'왼쪽을 그 객체의 타입과 연결시킨다고 합니다. myObject의 타입이 예를 들어서 MyType이라고 하면, 그 둘을 연결시키는 것이죠. 그 다음에는 MyIndexedProperty라는 이름을 MyType에서 쭉 훑어보면서 찾습니다.

하위 호환성을 유지하기 위해서, 컴파일러가 만약 검색중에 같은 이름을 가진 평범한 프로퍼티를 보더라도 무조건 그 프로퍼티와 호출을 바인딩하게 됩니다. 하지만, MyType에서 MyIndexedProperty라는 프로퍼티를 발견했다고 해도, MyType에 인덱서가 없다면 이 프로퍼티에 호출을 바인딩하지는 않습니다.

이렇게 인덱스 가능한 프로퍼티를 제외한 일반적인 바인딩이 실패 했다면, 컴파일러는 MyType이 ComImport타입인지 확인합니다. ComImport타입이 맞다면, MyType에서 사용가능한 인덱스 가능한 프로퍼티의 리스트를 만들어서, 후보군에 대해서 오버로딩 판별을 하는데, 이때의 판별 알고리즘은 평범한 프로퍼티와 동일하게 진행됩니다. 그저 이름으로 인덱서를 취급하듯이 하는 것이죠.

그리고 인덱스 가능한 프로퍼티를 사용했다고 해서 특별한 일이 일어나는 건 아닙니다. 그저 예전버전에서 값을 가져오려면 적어야 했던 내용을 그대로 생성해주는 syntactic sugar일 뿐이기 때문이죠. 이 부분은 아래의 예제를 보시면 좀 더 명확해 집니다.

//인덱스 가능한 프로퍼티를 사용한 경우
workSheet.Range["A1", "B3"].AutoFormat(
    Excel.XlRangeAutoFormat.xlRangeAutoFormatClassic2);

//기존버전에서 작업해야 했던 경우
workSheet.get_Range("A1", "B3").AutoFormat(Excel.XlRangeAutoFormat.xlRangeAutoFormatClassic2,
    Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing,
    Type.Missing);

위에서 보여드렸던 예제중에서 인덱스 가능한 프로퍼티를 사용한 코드를 리플렉터에서 보면 아래와 같았습니다.

workSheet.get_Range("A1", "B3").AutoFormat(XlRangeAutoFormat.xlRangeAutoFormatClassic2, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value);

똑같죠? 넵. syntatic sugar일 뿐이니까요. 낄낄낄.


- 마치면서

이번 포스트는 내용을 정리하면서 좀 힘들었습니다. 내공의 부족 때문인지 내용이 제대로 이해가 안됐기 때문이죠. 그런데 예제를 만들고 확인해보고 하니깐 조금씩 이해가 되더군요. 정리가 잘 된건지 모르겠습니다. 그럼 오늘은 여기까지~~!!!


- 참고자료

1. http://blogs.msdn.com/samng/archive/2009/11/03/com-interop-in-c-4-0-indexed-properties.aspx
2. http://msdn.microsoft.com/en-us/library/dd264733%28VS.100%29.aspx

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
- 어르신과 대화하려면 어케 해야 되는거임?

간단합니다. 말씀하시는 내용을 잘 경청하고, 대꾸는 딱 필요한 만큼만 하면 되는 거죠. COM은 아마 그동안 C#이 못마땅 했을 겁니다. VB.NET은 공손하게 필요한 말만 딱 하는데, C#은 'Type.Missing'이 어쩌고 저쩌고 주저리주저리 말이 많았기 때문이죠. C#도 이제 99년도 부터 출발했다고 볼때, 11년이나 되었으니 꽤 성숙한 셈이죠. 이제 예의를 갖추기 시작한 겁니다. 낄낄낄.


- C#의 버릇없던 옛 시절.

그러면 C#얼마나 버릇이 없었는지 아주 간단한 예제를 통해 알아보시죠.

using System;
using Word = Microsoft.Office.Interop.Word;

namespace ConsoleApplication3
{
    class Program
    {
        static void CreateIconInWordDoc()
        {
            var wordApp = new Word.Application();
            wordApp.Visible = true;

            object useDefaultValue = Type.Missing;

            wordApp.Documents.Add(ref useDefaultValue, ref useDefaultValue,
                ref useDefaultValue, ref useDefaultValue);
        }

        static void Main(string[] args)
        {
            CreateIconInWordDoc();
        }
    }
}


위 예제는 그냥 오피스 워드 창을 하나 띄우는 예제입니다. 그런데 평소에 오피스 COM 프로그래밍을 접해보지 못한 분이라면, 뭔가 특이한 점을 발견하셨을 겁니다. 'Type.Missing'같은 독특한 걸 object형 변수에 넣고, Add메서드에 여러번 반복해서 써넣기 때문이죠. 딱히 값을 리턴받아서 뭘 하는 것도 아닌 것 같고, Missing이라면 뭔가 없다는 것 같은데, 없는 값을 왜 저렇게 반복해서 넣어야 할까... 하는 생각이 드는 것이죠. 이제 COM이 왜 C#을 싫어했는지 아시겠나요? 말이 많았거든요.

지난 포스트에서 보셨듯이, C# 4.0은 다른 런타임과의 상호운용에 신경을 무척 많이 썼으며, 그중의 하나가 COM과의 상호운용이었습니다. C# 4.0에서는 COM과 대화할 때 좀더 말을 적게 하면서 예의를 갖춰서 대화를 하게 된 것이죠. 컴파일러가 COM 객체를 대상으로 작업하고 있다는 걸 눈치채는 순간, 컴파일러는 매개변수에 'ref'키워드를 안붙이고 메서드나, 인덱서, 프로퍼티에 넘길 수 있도록 해줍니다.

using System;
using Word = Microsoft.Office.Interop.Word;

namespace OfficeInteropExam2
{
    class Program
    {
        static void CreateIconInWordDoc()
        {
            var wordApp = new Word.Application();
            wordApp.Visible = true;

            object useDefaultValue = Type.Missing;

            wordApp.Documents.Add(useDefaultValue,
                useDefaultValue, useDefaultValue, useDefaultValue);
        }

        static void Main(string[] args)
        {
            CreateIconInWordDoc();
        }
    }
}


즉 C# 4.0에서는 위 처럼 ref를 빼고 작업할 수 있도록 도와줍니다. 그리고 컴파일러가 나중에 각 매개변수 앞에 ref를 붙여서 컴파일 하는 것이죠. 일종의 syntactic sugar인 것입니다. 그런데 여기에 지난 포스트까지 설명드렸던 Named and Optional Arguments를 이용하면 아예 매개변수를 생략할 수 있습니다.

using System;
using Word = Microsoft.Office.Interop.Word;

namespace OfficeInteropExam2
{
    class Program
    {
        static void CreateIconInWordDoc()
        {
            var wordApp = new Word.Application();
            wordApp.Visible = true;

            wordApp.Documents.Add();
        }

        static void Main(string[] args)
        {
            CreateIconInWordDoc();
        }
    }
}


지난 포스트까지 설명드렸듯이 아예 매개변수를 생략하면 기본값으로 설정된 값이 넘어가게 됩니다. 그러면, Add메서드의 각 파라미터에는 기본값이 어떻게 설정되어 있을까요? Add메서드의 정의를 보면 아래와 같습니다.

Document Add(ref object Template = Type.Missing, ref object NewTemplate = Type.Missing, ref object DocumentType = Type.Missing, ref object Visible = Type.Missing);

위에서 보시듯이, 기본값은 'Type.Missing'으로 설정되어 있습니다. 즉 매개변수를 생략하고 Add메서드를 호출하면, 기본값으로 Type.Missing이 넘어가고 컴파일러는 거기에 자동으로 ref를 붙여서 호출을 완성시켜 주는 것이죠. 지금은 겨우 파라미터가 4개정도라서 감흥이 없으실지도 모르겠지만, 파라미터가 30개정도 되는 메서드들을 자주만나다보면 아마 이 기능이 너무나도 고맙게 느껴지시겠죠? ㅋㅋㅋㅋ


- 마치면서

오늘은 아주 짧게 향상된 COM 프로그래밍에 대해서 알아봤습니다. COM이 안쓰이길 바랬음에 불구하고 많이 쓰이니 어쩔 수 없이 C# 4.0에서 COM에 대한 지원이 많이 들어갔다고 하는걸 보니, 역시 기술의 생명은 벤더에게만 달린게 아니라는 생각을 해봅니다. 그럼 오늘은 여기까지 하고 다음에 또 뵙죠!


- 참고자료

1. http://blogs.msdn.com/samng/archive/2009/06/16/com-interop-in-c-4-0.aspx
2. http://msdn.microsoft.com/en-us/library/dd264733%28VS.100%29.aspx
저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License


 

티스토리 툴바