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

저작자 표시 비영리 변경 금지
신고
- 어르신과 대화하려면 어케 해야 되는거임?

간단합니다. 말씀하시는 내용을 잘 경청하고, 대꾸는 딱 필요한 만큼만 하면 되는 거죠. 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
저작자 표시 비영리 변경 금지
신고

Welcome to Dynamic C#(19) - 위너 고르기.

C# 2010.05.13 09:00 Posted by 뎡바1

- 위너를 고르는 방식!

위너라니, 무슨 위너이야기 일까요? 넵. 메서드 오버로딩에서 호출에 맞는 메서드를 고를때, 어떤 경우에 어떤 메서드가 더 적합한지 고르는, 즉 메서드 오버로딩 중에서 위너를 고르는 거에 대한 이야기 입니다. 아마도 이런 경우는 생각보다 자주일어날 것 같은데요. 과연 컴파일러는 어떤 방식으로 위너를 고를까요? 키로? 얼굴로? 능력으로? 한번 알아보시죠.


- 첫번째 경우

일단 두 메서드의 파라미터 개수가 같다고 할때는 매개변수가 어떤 타입의 파라미터로 형변환하는게 더 나은것인지를 기준으로 판단합니다.

class C
{
    public void Foo(object o) { Console.WriteLine("object"); }
    public void Foo(int x) { Console.WriteLine("int"); }

    static void Main(string[] args)
    {
        C c = new C();
        c.Foo(10);
    }
}


그럼 위의 예제에서는 누가 위너가 될까요? 10은 정수니까, object로 형변환도 가능하고, int로도 형변환이 가능합니다. 하지만 정수는 object보다는 int로 형변환 되는게 더 적합한 형변환이죠. 그래서 여기서는 'Foo(int x)'가 위너가 됩니다. 'Foo(int x)' ㅊㅋㅊㅋ


- 두번째 경우

두번째 경우는 파라미터 배열이 끼어듭니다.

class C
{
    public void Foo(int x, int y) { Console.WriteLine("int twins"); }
    public void Foo(params int[] x) { Console.WriteLine("params"); }

    static void Main(string[] args)
    {
        C c = new C();
        c.Foo(10, 20);
    }
}


첫번째 예제를 약간 변형시킨 건데요. 컴파일러는 파라미터 배열을 보자마자 이 파라미터 배열을 확장시켜서 메서드의 시그니처를 'Foo(int x_1, int x_2)'같이 만들고 이 시그니처를 가지고 오버로딩의 후보군에 끼워넣는다고 합니다. 그런데 컴파일러가 파라미터 배열을 확장만 시키는게 아니라, 파라미터 배열에서 확장되었음을 표시한다고 하네요. 그리고 파라미터에서 확장된 시그니처의 경우는 오버로드 판별에서 2등급으로 취급된다고 합니다. 즉 메서드 시그니처가 동일할 경우에 파라미터 배열은 2등급이기 때문에, 다른 일반적인 1등급 파라미터에 우선순위에서 밀리게 된다고 합니다. 위의 예제에서는 'Foo(int x, int y)'가 위너네요. ㅊㅋㅊㅋ


- 세번째 경우

이제 드디어 지금까지 열심히 이야기 해온 옵셔널 파라미터가 끼어들 차례입니다.

class C
{
    public void Foo(int x) { Console.WriteLine("int"); }
    public void Foo(int x, int y = 0, int z = 10)
    { Console.WriteLine("optionals"); }

    static void Main(string[] args)
    {
        C c = new C();
        c.Foo(10);
    }
}


위 예제는 쫌 난감합니다. 'c.Foo(10)'라는 호출만 보자면, 둘다 똑같이 해당되기 때문이죠. 첫번째 Foo는 시그니처가 완전히 일치하고, 두번째 Foo도 x를 제외한 값은 모두 기본값이 사용될 수 있으니까요. 이 경우에는 파라미터 배열과 같이 옵셔널 파라미터를 2등급으로 취급한다고 합니다. 즉 첫번째 Foo메서드가 옵셔널 파라미터가 하나도 없기 때문에 첫번째 Foo가 위너가 되는 것이죠. 그런데 만약에 첫번째 메서드에도 옵셔널 파라미터가 있다면 어떻게 될까요?

class C
{
    public void Foo(int x, int y = 0) { Console.WriteLine("optional1"); }
    public void Foo(int x, int y = 0, int z = 10)
    { Console.WriteLine("optional2"); }

    static void Main(string[] args)
    {
        C c = new C();
        c.Foo(10);
    }
}


위와 같이 작성된 경우 말이죠.


이런 에러를 보게됩니다. 즉, 둘간의 차이를 분별해낼 수 없기 때문에 모호한 호출이라는 것이죠.


- FAQ!

옵셔널 파라미터는 그동안 아주 꾸준히 C#에 추가해달라고 요청하던 기능입니다. 특히 오피스같은 COM과 연동하는 작업을 하는 프로그래머들이 많이 요청을 했었습니다. 왜 이런 기능을 예전에 안하고 지금하느냐? 하는 질문이 있을 법한데요. C#개발팀의 Sam Ng가 답변한 내용을 옮겨볼까 합니다.

1. 왜 이걸 좀 더 일찍하지 않았냐?

- 왜 이걸 좀 더 일찍 하지 않았느냐 하면 말이죠. 우린 진짜 이 기능이 C#에 포함되지 않았으면 했습니다. 이걸 그동안 계속해서 미뤄온 건, 이건 우리가 원했던 패러다임이 아니었기 때문이었죠.

2. 그럼 왜 지금은 이걸 추가했느냐?

- 이게 다 COM 때문이죠. 진짜 이건 사라지지를 않더라구요! 이걸 없앨려고 노력했지만, 사람들은 계속 이걸 사용하고 있고, 앞으로도 계속 사용하려고 하더군요. C#이랑 COM이랑 무슨 관계냐구요? 오피스. 오피스 PIAs때문이죠. 오피스 PIAs는 대부분 30개정도의 파라미터를 갖는 메서드로 이루어져 있죠. 그 파라미터의 대부분은 옵션이구요. 대부분의 경우에는 한개정도의 매개변수만 적어주고 나머지는 다 기본값을 사용하면 되는거죠.

이제 Named and Optional Parameters를 통해서 옵션인 파라미터는 안적고도 메서드를 호출할 수 있죠. 오피스 메서드를 호출할때도 모든 매개변수마다 Type.Missing같은거 안적어주고도 호출할 수 있는거죠. 그리고 매개변수에 해당하는 파라미터 이름을 적을 수 있기 때문에, 딱 필요한 거만 매개변수로 넘겨주고, 나머지는 생략할 수 있죠.

그리고 ref 없이 COM을 사용할 수 있도록한 기능과 조합해서 사용하면 COM 코드가 더 간결해지고 지루한 작업은 매우 줄어들겁니다. 컴파일러가 옵션인 ref 파라미터에 넘겨줄 임시값을 만들어서 매개변수를 넘겨주거든요.

제가 예전에 이야기 했듯이 C# 4.0의 큰 테마중의 하나가 다른 런타임(COM, 동적언어 등)과의 상호운용이에요. 그런 테마가 이런 기능을 반드시 갖추도록 했던거죠.


- 마치면서

아~ 이제 Named and Optional Parameters에 대해서 할말은 다 한것 같습니다. 자료를 읽으면서 저도 많이 배우고 재밌는 내용도 많이 읽었네요. 여러분은 어떠셨나요? 호호호호-_- 그럼 오늘은 여기까지 하고~ 다음에 또 다른 이야기 가지고 오겠습니다!


- 참고자료

1. http://blogs.msdn.com/samng/archive/2009/04/17/named-and-optional-arguments-ties-and-philosophies.aspx
저작자 표시 비영리 변경 금지
신고
- 무슨일인데 그러냥?

네. 언제나 기존의 질서안에서 새로운 변화를 가져오려고 하면 새로운 문제들이 생기기 마련이죠. 오늘은 매개변수에 이름을 붙이면서 생겨난 문제와 내용에 대해서 설명을 드려보려고 합니다. 우끼끼끼!!


- 이름은 다 어디서 가져오놔?

우선, CLR이 파라미터 이름은 메서드 시그니처의 일부로 보지 않기 때문에 A라는 메서드를 오버라이드해서 B라는 메서드를 작성한다고 할때, A와 B의 파라미터 이름이 다르다고 해도 전혀 문제되지 않습니다.

public class Animal
{
    public virtual void Eat(string foodType = "Rice")
    {
    }
}

class Monkey : Animal
{
    public override void Eat(string bananaType = "Rainbow Banana")
    {           
    }

    static void Main(string[] args)
    {
        Monkey m = new Monkey();
        Animal a = m;

        m.Eat(bananaType: "Black Banana");
        a.Eat(foodType: "Hamburger");
    }
}


위의 예제를 보시면, Animal을 Monkey가 상속해서 Eat을 오버라이드 하고 있습니다. 하지만 메서드 간의 파라미터 이름은 전혀 문제가 되지 않습니다. 그런데요, 이름이 틀리게 되면 또 하나의 의문점이 생길 수 있습니다. 언제 어떤 이름이 쓰이는 걸까요? 해답은 생각보다 간단합니다. 수신자를 중심으로 생각하는 것이죠.

여기서 수신자란 메서드의 호출대상이 되는 객체를 말하는데요. 'm.Eat()'에서는 m이 수신자가 되는 것이죠. 즉, 'm.Eat'에서 m은 정적타입인 Monkey타입의 객체입니다. 그래서 m.Eat에서는 Monkey클래스에 정의된대로, 'bananaType'을 파라미터의 이름으로 가져옵니다. 그리고 a.Eat에서는 Animal클래스에 정의된대로, 'foodType'을 파라미터의 이름으로 가져오는 것이죠. 주의깊게 보셨다면, m에서 생성한 객체를 그대로 a에 넣어주는 걸 보실 수 있습니다. 즉, 동일한 객체라는 의미인데요. 동일한 객체에 대해서라도, 수신자를 중심으로 파라미터의 이름을 가져온다는 이야기가 되는거죠.


- 이름을 가져올 때 안에서 벌어지는 일.

class Calc
{
    static double CalcRatio(double source,
        double factor1 = 0.87,
        double factor2 = 1.0)
    {
        return source * factor1 * factor2;
    }

    static void Main(string[] args)
    {
        CalcRatio(92.1, factor2:1.11);
    }
}


위와 같은 코드를 가지고 생각을 해보겠습니다. 실제로 위 코드가 실행될 때까지 어떤 일이 벌어지는지 말이죠. 우선 컴파일러가 'CalcRatio'를 호출하는걸 보게되면, 이름을 붙이지 않은 매개변수가 이름을 붙인 매개변수보다 앞쪽에 있는지 확인을 합니다. 그리고 매개변수에 붙인 파라미터의 이름이 중복되지 않는지 확인합니다. 같은 파라미터에 두개의 매개변수를 넘길수는 없으니 말이죠. 그리고는 적용가능한 모든 후보메서드군을 생성합니다. 위의 예제에서는 딱 한개뿐이죠. 그 후에는 각각의 후보메서드에 대해서 몇가지 검사를 합니다.

일단 메서드 호출에 적혀있는 파라미터의 이름이 후보군에 있는 메서드의 파라미터 이름과 동일한지 검사합니다. 여기서는 'factor2'의 이름을 각 후보메서드가 파라미터로 가지고 있는지 확인하겠죠. 그리고 이름이 붙은 모두 매개변수와 파라미터가 일치하는지 확인합니다. 즉, CalcRatio의 파라미터 중에 이름이 붙지 않은 매개변수를 받지 못한 factor1, factor2는 이름이 붙은 매개변수를 받아야 한다는 것이고, 같은 파라미터에 중복되는 매개변수가 없어야 한다는 것이죠.

만약에 이름이 붙은 매개변수나 이름이 붙지않은 매개변수 어느 것도 받지 못한 파라미터가 있다면, 컴파일러는 그 파라미터가 옵셔널 파라미터인지 검사합니다. 만약에 그 파라미터가 옵셔널 파라미터라면, 기본값을 파라미터에게 넘겨줄 매개변수로 사용하게 됩니다.

이런과정을 겨처서 매개변수 목록이 정리되면, 컴파일러는 늘 하듯이, 각 매개변수가 형변환에 문제가 없는지 확인을 합니다. 위의 예제에서 정리된 매개변수의 목록은 ['92.1', '0.87', '1.11']가 되겠죠.

이 모든 과정은 철저하게 컴파일 시점에서 벌어지는 'syntactic sugar'입니다. syntactic sugar는 그저 프로그래머의 수고를 덜어주는 역할을 하는 기능을 뜻하는 데요, 지금까지 설명드린 'Named and Optional Parameters'는 새로운 참조를 요구하지도 않고, 새로운 호환성 문제를 만들지도 않습니다. 생성된 IL을 보면, 그냥 일반적으로 호출하는 모양과 차이가 없기 때문이죠. 즉, 컴파일러가 위에서 설명드린 과정을 거쳐서 정리된 매개변수의 목록을 만들고 나면, 프로그래머가 원래 똑같은 매개변수목록으로 메서드를 호출한 것 처럼 처리를 합니다. 그래서 컴파일이 되고 난 후에, 메서드의 파라미터 이름이 바뀌거나 새로운 옵셔널 파라미터가 추가되어도 아무문제 없이 동작하는 것이죠.


- 중요한 거 한가지만 더!! 캬캬캬

class Calc
{
    static int GetNum1()
    {
        Console.WriteLine("GetNum1");
        return 1;
    }

    static int GetNum2()
    {
        Console.WriteLine("GetNum2");
        return 1;
    }

    static int GetNum3()
    {
        Console.WriteLine("GetNum3");
        return 1;
    }

    static void DoSth(int num1, int num2, int num3)
    {
    }

    static void Main(string[] args)
    {
        DoSth(num3: GetNum3(),
        num1: GetNum1(),
        num2: GetNum2());
    }
}


위와 같은 예제가 있다고 했을때요, 아마도 컴파일러는 매개변수의 순서를 재정렬해서 num1, num2, num3의 순서로 각 파라미터에 넘겨줄 것 같은데요. 메서드안의 GetNum시리즈는 어떤 순서로 평가될까요? 써있는 순서대로 앞에서 뒤로 할 것같다고 생각하셨다면 정답! 입니다. 처음에 GetNum3, GetNum1, GetNum2의 순서로 말이죠. 결과를 보시면 명확합니다.


내부적으로는 각 파라미터에 대한 표현식의 결과를 저장할 공간을 임시로 만들고, 각 표현식의 결과를 저장한 후에, 그 임시값들을 순서에 맞게 재정렬해서 파라미터에게 넘겨준다고 합니다. 재밌지 않나요? 저만 그런가효? 호호호호-_-;;;;


- 마치면서

이제 Named and Optional Parameter(도대체 한글로 뭐라고 써야할지 감이 안잡히네요-_-)에 대해서 기본적인 이야기는 한 것 같은데요. 처음에는 '그냥 파라미터에 기본값을 줄 수 있고, 매개변수를 넘겨줄 때 순서를 바꿔서 줄 수도 있다' 이정도 인줄 알았는데, 공부를 하다보니 생각보다 복잡하기도 하고 재미있는 내용이 많아서 글로 정리하면서도 즐거웠습니다. 오늘은 여기까지 하죠~~~~~!


- 참고자료

1.
http://blogs.msdn.com/samng/archive/2009/04/01/named-arguments-and-overload-resolution.aspx
저작자 표시 비영리 변경 금지
신고
- 이젠 dynamic을 벗어나서!

문득 제가 거의 1년 가까이 dynamic만 이야기를 했다는 걸 깨달았습니다.-_-;;;; C# 4.0에 dynamic만 추가된게 아닌데 말이죠;;; 반성을 하면서! 이제 dynamic말고 다른 이야기를 좀 하겠습니돠.


- 파라미터에 기본값을 설정하는거지.

프로그래밍을 하다보면, 여러가지 파라미터를 가지는 메서드를 작성하는 경우가 많은데요. 호출시에 꼭 매번 넘겨줘야 하는 파라미터가 있는가 하면, 대부분의 경우 그냥 한가지 값으로만 쭉~ 사용하는 파라미터도 있습니다. 그래서 C#에서는 늘~ 메서드 오버로딩을 통해서 파라미터가 축약된 메서드를 정의해주고, 그 메서드 안에서 원래의 메서드를 호출하면서, 기본값을 매개변수로 넘겨주는 형태를 취했습니다. 그래서 많은 프로그래머들이 메서드를 정의할 때, 파라미터에 기본값을 정의할 수 있게 해달라고 요청했습니다. 그리고 그 메서드를 호출할 때, 기본값을 그대로 쓰는 경우에는 매개변수를 생략할 수 있게 해달라고 말이죠.

C# 4.0에서 파라미터에 기본값을 설정하는데는 두가지 방법이 있는데요,

class C
{
    static int Add(
        [DefaultParameterValueAttribute(10)] int num1,
        int num2 = 30)
    {
        return num1 + num2;
    }

    static void Main(string[] args)
    {
        Console.WriteLine(Add(15));
    }
}


위의 코드를 보시면, num1에는 DefaultParameterValueAttribute라는 어트리뷰트가 붙어있는 걸 보실 수 있는데요. 이건 그냥 하위호완성과 COM, VB와의 상호운용을 위해서 만들어진거라고 합니다. 이 방법은 권장되지 않는데요, 왜냐하면 이 어트리뷰트는 특정 파라미터에 기본값이 있다는 건 명시하지만, 그 파라미터가 옵션인지 아닌지는 명시할 수 없기 때문입니다. 즉 컴파일러 조차 이 파라미터가 옵션으로 생략가능한지 인식하지 못합니다. 그래서 결국에는 명시된 기본값은 아예 사용되지도 못하는 거죠.

그리고 'num2' 파라미터를 보시면 이 부분은 두가지 의미가 있습니다. 첫째는 컴파일러에게 이 파라미터가 옵션으로 생략가능하다는 걸 알려주는 거구요, 둘째는 프로그래머가 이 파라미터에 아무 값도 안 넘겨주때 사용할 수 있는 기본값을 컴파일러에게 알려주는 것입니다. Add메서드를 호출할 때 인텔리센스를 보면요,


위에서 말씀드린대로, 'num1' 파라미터에 대해서는 기본값은 설정이 되어있지만, 옵션이라는 표시가 없기 때문에 생략할 수 없습니다. 그렇기 때문에 어트리뷰트에 명시된 기본값은 아예 사용되지 않는 것이구요. 그리고 'num2' 파라미터를 보시면, 기본값이 30이라고 표시되는 걸 보실 수 있습니다. 그리고 이렇게 표시된 파라미터는 옵션으로 생략가능한 것이구요. 위의 코드를 실행하면, 15와 'num2' 파라미터의 기본값인 30이 더해져서 45라는 결과가 나오게 됩니다.


- 파라미터에 기본값을 설정하면 어케 되는고얌~?

위의 Add메서드를 리플렉터에서 보면 아래와 같습니다.

private static int Add([DefaultParameterValue(10)] int num1,
                              [Optional, DefaultParameterValue(30)] int num2)
{
    return (num1 + num2);
}

'num1' 파라미터는 위에서 선언해준 어트리뷰트가 그대로 설정되어 있는 걸 보실 수 있구요. 'num2' 파라미터를 보시면, 기본값을 설정하는 게 실제로는 두가지 일을 한다는 걸 알 수 있습니다. 파라미터에 DefaultParameterValue 어트리뷰트를 통해서 기본값을 설정하고, 이 파라미터가 옵션으로 생략가능함을 나타내는 Optional이라는 표시도 하게 되는 것이죠. 이런 두가지 어트리뷰는 이미 CLR에 존재했다고 합니다. VB.NET에서는 이미 제공되던 기능이니깐 당연한 이야기겠죠.

여담이지만, VB.NET과 C#개발팀이 통합되었다고 합니다. 서로 같이 크면서 동일한 기능을 제공하고자 하는 'coevolution'전략을 위함이라고 하는데요. 그 덕분일까요? C#에도 오늘 소개해드리는 기본값을 설정하는 기능이 추가되었고, VB.NET에도 C# 3.0에서 추가되었던 '컬렉션 이니셜라이저'나 '자동으로 구현된 속성'같은 기능이 추가되었습니다. 자세한 내용은 여기를 참고하시면 되겠네요.

그런데 기본값을 설정할 때, 몇가지 규칙이 있는데요. 첫째로는 옵션으로 생략가능한 파라미터는 생략불가능한 파라미터를 모두 선언한 뒤에 나와야 합니다.


위와 같이 옵션인 파라미터가 먼저 나오고, 뒤에 반드시 필요한 파라미터를 선언하면, 옵셔널 파라미터는 반드시 꼭 필요한 파라미터들 뒤에 나와야 한다고 경고메세지가 뜨는 걸 볼 수 있습니다. 그리고 ref나 out으로 설정된 파라미터는 기본값을 설정할 수 없습니다. 왜냐하면, ref나 out에 대응되는 상수값이 존재하지 않기 때문이죠.

그리고 호출시에 옵셔널 파라미터로 설정된 파라미터에 매개변수를 넘겨주지 않으면, 컴파일러는 DefaultParameterValue 어트리뷰트에 설정된 기본값을 가지고 와서, 그 값을 매개변수로 해서 메서드를 호출하는데 사용하게 됩니다.


- 매개변수에게 이름을 허 하여뢋!

형을 형이라고 부르지 못하고, 아버지를 아버지라 부르지 못한 건 아니지만, 매개변수는 늘 이름없는 설움을 겪어야 했습니다. 파라미터는 이름이라도 갖고 있었죠. 태어나서 이름하나 세상에 남기지 못하는게 얼마나 슬픈....여기까지 하고 이야기 계속 하겠습니다.-_-;

이제 이름붙인 매개변수를 사용하게 되면, 이 모든 장점을 제대로 활용할 수 있게 됩니다. 만약에 모든 파라미터가 옵셔널 파라미터 라면, 그 중에 값을 넘겨주고 싶은 것들만 이름을 붙여서 매개변수를 넘겨줄 수 있는 것이죠. 기존의 오피스등의 COM 프로그래밍을 할 때, 대부분의 값들이 옵션으로 생략하능한 파라미터지만, C#에서는 그것들을 생략할 방법이 없어서 의미도 없는 값을 반복해서 넘겨줘야 했던 걸 생각하면 굉장히 편해질거라는 생각도 드네요.


이름붙인 매개변수는 메서드를 호출할 때 사용되는 데요. 예제를 보시면요,

class C
{
    static double CalcRatio(
        double fact1 = 90.0,
        double fact2 = 0.9887,
        double fact3 = 33.211)
    {
        return fact1 * fact2 * fact3;
    }

    static void Main(string[] args)
    {
        Console.WriteLine(
            CalcRatio(fact1:40.12, fact3:13.11)
            );
    }
}


위의 예제를 보면, 세상에 이런 엉터리 계산식이 있을진 모르겠지만 아무튼 뭔가의 비율을 계산하는 메서드인 CalcRatio가 있습니다. 계산할 때 일반적으로 고정된 상수들이 있을 수 있는데요, 그런 경우를 위해서 기본값을 설정해두었습니다. 그리고 호출하는 부분은 보시면, 'fact1', 'fact3' 파라미터에 넘겨질 값들에 각각 이름을 붙인 것을 볼 수 있습니다. '40.12'는 'fact1'파라미터에 넘겨질 매개변수이고, '13.11'은 'fact3'파라미터에 넘겨질 매개변수 인 것이죠.

매개변수에 이름을 붙이는 건, 꼭 기본값이 있는 옵셔널 파라미터에만 한정되는 않습니다.

class C
{
    static double CalcRatio(
        double superfactor,
        double fact1 = 90.0,
        double fact2 = 0.9887,
        double fact3 = 33.211)
    {
        return superfactor * (fact1 * fact2 * fact3);
    }

    static void Main(string[] args)
    {
        Console.WriteLine(
            CalcRatio(fact1:(40.12 / 2), fact3:13.11, superfactor:100)
            );
    }
}


위의 예제와 같이 기본값이 없는 일반 파라미터에 넘겨줄 매개변수에도 이름을 붙일 수 있으며, 매개변수의 순서는 파라미터의 순서와는 전혀 상관없이 배열할 수 있습니다. 그리고 'fact1'의 매개변수처럼 원래 매개변수로 넘겨줄 때 할 수 있는 것 처럼 아무 표현식이든지 올 수 있습니다.


- 마치면서

그동안 아주 오랫동안.... 게으름과 겹치면서 너무 오랫동안 dynamic에 대해서만 이야기를 해왔는데요. 문득 돌아보니 dynamic외에도 다룰 내용이 좀 더 있다는 걸 깨달았습니다.-_-;;;; 머리가 둔하면 이렇죠. 하하하하하>ㅇ<


- 참고자료

1. http://blogs.msdn.com/samng/archive/2009/02/03/named-arguments-optional-arguments-and-default-values.aspx
저작자 표시 비영리 변경 금지
신고
- Long time no see~

오랜만이죠~? 다행히 기다려주신 분이 없는 거의 없는 관계로 마음은 불편하지 않았습니다. 그런데 왜 눈물이 아흙.... 아직 못한 이야기가 조금 있는거 같아써! 조금 더 이야기를 하도록 할께영~ 호호호호-_-


- 상황1. dynamic타입의 변수에 들어있는 값을 변환시키기

다음과 같은 코드가 있다고 가정을 했을 때요,

static void Main()
{
    dynamic d = 10;
    d++;
}

어떤 일이 벌어질까요? d에는 11이라는 값이 있어야 할 것 같지만, dynamic타입은 실제로는 object타입이기 때문에 다른 결과가 나옵니다. dynamic타입이 실제로는 object라는 건 이전에 이야기 했던 내용인데요, 자세한 내용은 이전포스트를 참고하시면 되겠습니돠. 아무튼 처음에 d에 10을 넣을 때, int에서 object로 박싱이 일어나구요, 두번째 줄에서 d에서 언박싱한 값을 가지고 ++연산을 수행합니다. 하지만 이 값은 다시 박싱되어서 저장되지는 않는다는 게 문제입니다. 그러면 결과는 여전히 10을 가리키겠죠.

그런데 이런 문제는 런타임 바인더의 구조 덕분에 해결이 가능했다고 하는데요. 이전에 말씀드렸듯이 런타임 바인더는 동적인 구문을 적절한 객체와 연산으로 바인딩하고 그 결과를 Expression Trees의 형태로 DLR에게 리턴해줍니다. Expression Trees의 장점은 목표로 하는 형태로 변환되기 전에, 여러가지 변환이나 최적화가 용이하다는 점이 었는데요, Expression Trees에 박싱된 값을 언박싱하고 값을 변화시키는 요소가 있고, 그 값을 다시 박싱해서 저장하는 것도 있다고 합니다. 그래서 이런형태의 동적인 표현식을 제대로 처리할 수 있다고 하네요.


- 상황2. 중첩된 구조체 연산

이번 문제는 조금 더 알쏭달쏭한데요. '.'으로 여러번 연결된 표현식을 생각해보면요, 각각 부분별로 쪼개셔서 바운딩이 됩니다. 즉 A.B.C.D같은 표현식이 있다고 하면요, A.B에 대한 사이트를 만들고, 다시 그 결과를 .C의 수신자로 하는 사이트를 만들고, 다시 그 결과를 .D의 수신자로 하는 사이트를 만듭니다. 꽤나 현명하게 잘 만든거 같다는 생각이 들긴하는데요. 원래 컴파일러가 하는거랑 같은 방식이기도 하구요. 그런데 문제는 런타임의 구조상 ref형식으로 값을 리턴할 수 없다는 제약때문에 생깁니다. 물론 이런 제약은 CLR의 제약은 아닙니다. 다만 닷넷의 언어중에서 ref 리턴을 지원하는 언어가 없기 때문인데요. 그 말은 만약에 값형 변수에 대해서 연속적으로 '.'으로 연결된 표현식이 있다면, 대상이 되는 변수의 값은 박싱이 되면서 복사본이 생깁니다. 그리고 이후에 '.'로 연결된 것들은 그 복사본을 대상으로 연산이 수행된다는 것이죠. 예제를 보시면요,

public struct S
{
    public int i;
}

class D
{
    public S s;
    static void Main(string[] args)
    {
        dynamic d = new D();
        d.s = default(S);
        d.s.i = 10;
        Console.WriteLine(d.s.i);
    }
}


10이 결과로 찍힐거라고 예상할 수 있지만, 결과는 아래와 같습니다.


앞에서 설명드린대로, 'd.s.i = 10'에서 구조체 S가 박싱되면서 복사본이 생겼고, 그 복사본의 i에 10을 대입했기 때문에, 원래의 'd.s'의 i값에는 변화가 없는 것이죠. 이 문제는 참고하고 있는 Sam Ng의 2008년 12월 15일자 글에서 어떻게 할지 고민중이라고 적혀있었는데요. 출시된 VS2010에서 확인해본 결과 아무런변화가 없어서, 그대로 두기로 결정한 것으로 보입니다. 뭐 결국 핵심은 dynamic은 object랑 비슷하기 때문에 박싱이 일어난다는 점입니다.


- 상황3. 명시적으로 구현된 인터페이스의 메서드

우선 명시적으로 구현된 인터페이스의 메서드가 뭔지 부터 이야기를 해야 할 것 같습니다.



위 그림을 보면, S가 IEnumerable를 구현한다고 선언을 한 상태인데요. 인터페이스를 구현하는 방식에 'Implement Interface'와 'Implement Interface Explicitly'가 있는 걸 볼 수 있습니다. 전자가 우리가 흔히 인터페이스를 구현할 때 써온 암시적 인터페이스 구현이구요. 후자가 여기서 말씀드릴 명시적 인터페이스 구현입니다. 우선 위의 두 경우에 코드 모양이 어떻게 틀린지 확인해보도록 하지요.

public class S : IEnumerable
{
    public int i;

    #region IEnumerable Members

    public IEnumerator GetEnumerator()
    {
        throw new NotImplementedException();
    }

    #endregion
}

- 암시적 인터페이스 구현의 경우

public class S : IEnumerable
{
    public int i;

    #region IEnumerable Members

    IEnumerator IEnumerable.GetEnumerator()
    {
        throw new NotImplementedException();
    }

    #endregion
}

- 명시적 인터페이스 구현의 경우

틀린 점을 발견하셨나요? 첫째로 명시적 인터페이스 구현의 경우, 메서드 이름인 GetEnumerator앞에 인터페이스의 이름이 '.'과 함께 붙어있습니다. 이 메서드가 어떤 인터페이스를 통해 구현된 건지 명시적으로 보여주는 것이죠. 그리고 한정자가 없으므로 private입니다. 그러므로 이 메서드는 인터페이스를 구현했지만 밖으로 노출이 되지 않습니다. 이렇게 명시적인 인터페이스 구현을 하는 경우에는 몇가지가 있을 수 있는데요. 이에 대한 더 자세한 설명은 유경상 수석님의 글을 참고하시면 매우 자세하게 아실 수 있습니다. 그럼 명시적 인터페이스 구현에 대한 설명은 여기까지로 하구요, 이게 dynamic과 무슨 관련이 있는지 알아보도록 하겠습니다. 아래와 같은 예제가 있다고 할때 말이죠,

public interface IFoo
{
    void M();
}

class C : IFoo
{
    #region IFoo Members

    void IFoo.M()
    {
        Console.WriteLine("C.M()!!");
    }

    #endregion
}

class D
{       
    static void Main(string[] args)
    {
        dynamic d = new C();

        d.M();
    }
}


예상으로는 "C.M()!!"이라는 메세지가 출력될 것 같기도 한데요. 그런데 앞서 말씀드렸던 명시적 인터페이스 구현의 특성 때문에, 런타임에 클래스C에서는 M이라는 이름의 메서드를 찾을수가 없다고 합니다. 그래서 런타임 바인더가 호출에 대해서 묶을 수 있는 IFoo라는 타입을 찾을 수 없다고 하네요. 이런 문제 때문에 위의 코드는 아래와 같은 에러를 내게 됩니다.


C라는 타입에 M의 정의가 없다는 에러메세지를 내면서 호출은 실패하게 됩니다.


- 마치면서

이제 비주얼 스튜디오 2010이 정식으로 출시되면서 C# 4.0에 대한 이야기도 현실과 매우 가까운 이야기가 되었습니다. 다만, 제 능력부족으로 글의 내용은 비현실적인거죠-_-;; 아무쪼록 도움이 되셨기를 바라면서 오늘은 여기서 끗~!


- 참고자료

1. http://blogs.msdn.com/samng/archive/2008/12/15/dynamic-in-c-vi-what-dynamic-does-not-do.aspx
2. http://www.simpleisbest.net/archive/2008/06/23/2423.aspx
저작자 표시 비영리 변경 금지
신고

Welcome to Dynamic C#(15) - A/S for dynamic.

C# 2010.05.03 09:00 Posted by 뎡바1

- 긴급 A/S 출동!

제가 글을 쓰면서 공부했던 내용들이 많이 바뀌었습니다-_- 그래서 저도 좀 뒤늦게 그 업데이트를 확인했구요~ 그래서 바뀐 내용에 대해서 A/S들어갑니다~~!


- 오버로드 판별 A/S!

아마 이 시리즈를 관심있게 읽어주신 분이라면!... 아흙 왜 또 눈물이... 아무튼 읽어주신 분이라면 유령메서드라는 걸 보셨을텐데요. 그게 너무 복잡하다보니, 유령메서드랑 관련한 내용은 하나도 채택되지 못했다고 합니다. 그리고 굉장히 간단한 규칙으로 정리를 했습니다. 만약에 어떤 메서드를 호출할 때 dynamic타입의 매개변수가 끼어있다면, 그 메서드에 대한 호출은 동적으로 디스패치된다는 것입니다. 그리고 런타임에서 dynamic타입의 매개변수의 실제값에 맞는 타입을 선택한다고 합니다.

class C
{
    static void M(dynamic d) { Console.WriteLine("dynamic"); }
    static void M(string s) { Console.WriteLine("string"); }
    static void M(int i) { Console.WriteLine("int"); }

    static void Main(string[] args)
    {
        dynamic d = "test";
        M(d);
    }
}


위와 같은 예제를 보면, M에 대한 호출은 런타임에 디스패치 됩니다. 그리고 어떤 M이 선택될지는 d의 실제값에 달여있는데요, 여기서는 d의 실제값이 string이기 때문에, 'M(string)'이 선택됩니다. 그러면, M(dynamic)은 어떻게 되는 걸까요? 바뀐 규칙에서 파라미터에서 dynamic타입이 있는 건, object타입이 있는것과 동일한 의미를 갖습니다. 즉 적합한 오버로드가 없을 때, 가장 나중에 선택이 되게 되겠죠.


- 대입형변환 A/S!

dynamic이 끼어있는 형변환 규칙도 약간의 변화가 있는데요, 그 규칙은 아래와 같습니다.

1. 모든 타입에서 dynamic타입으로 암시적 형변환이 가능하다. 기본적으로 object로 암시적 형변환이 가능한 타입이라면, dynamic으로도 암시적 형변환이 가능하다.

2. dynamic타입에서는 dynamic과 object를 제외한 다른 어떤 타입으로도 암시적 형변환이 불가능하다.

3. 하지만, 모든 '동적 표현식'은 다른 모든 타입으로 암시적 형변환이 가능하다.

4. 만약에 어떤 타입의 차이점이 dynamic과 object뿐이라면, 서로간에 암시적 형변환이 가능하다.

2, 3번에 대해서 좀 더 들여다 보면요, 타입의 형변환과 표현식의 형변환은 과연 뭐가 틀린걸까요? 일단 표현식의 형번환은 이때까지 '대입 형변환'이라고 불렀던 것을 가능하게 해줍니다. 예제를 하나 보면요.

string s = d;

이 형변환은 3번 규칙 때문에 성립합니다. 이 형변환은 동적 표현식에서 string으로의 형변환이지, dynamic타입에서 string으로의 형변환이 아니기 때문이죠. 이외에도 return, foreach, 프로퍼티에 값설정하기 등의 대부분의 경우는 이렇게 처리됩니다.

그렇다면 dynamic타입에서 string타입으로 형변환이 없어야 하는 이유는 뭘까요? covariance를 통해서 컴파일러가 이 형변환을 못하게 막는 걸 한번 확인해보겠습니다.

class C
{
    static void Main()
    {
        IEnumerable<dynamic> ied = null;
        IEnumerable<string> iei = null;
        var x = M(ied, iei);           
    }
    static T M<T>(IEnumerable<T> x, IEnumerable<T> y) { return default(T); } 
}

위 예제는 문제없이 컴파일 됩니다. 그리고 메서드의 타입유추 과정을 통해서 T를 dynamic이라고 골라내는 과정을 거칠텐데요, 그 과정은 아래와 같습니다.

1. 첫번째 매개변수는 IEnumerable<dynamic>이다. 그러므로 dynamic은 T의 후보가 된다.

2. 두번째 매개변수는 IEnumerable<string>이다. 그러므로 string은 T의 후보가 된다.

3. T의 후보군인 { dynamic, string }을 가지고 고민하는 과정에서 둘 사이의 관계를 보는데. string에서 dynamic으로의 형변환은 있지만, 그 반대는 성립하지 않는다. 그러므로 좀 더 일반적인 타입인 dynamic을 고르게 되는 것이다.

위 과정의 3번 단계에서 만약 dynamic에서 string으로 형변환이 가능했다면, 둘 중에 어떤 타입이 더 일반적인 타입인지를 결정할 수 없게 됩니다. 그렇다면 메서드 타입유추는 모호환 둘의 관계 때문에 실패하게 되겠죠. 그게 바로 dynamic에서 다른 타입으로의 형변환이 금지되어야 하는 이유입니다.

그리고 대입형변환이라는 용어는 아마 살아남지 못한 것 같습니다. C# 4.0 명세서에서 그 용어를 발견할 수 없었기 때문입니다. 그래도 혹시나 싶어서 Chris Burrows에게 질문을 남겨놨으니, 답이오면 반드시 알려드리겠습니다.


- 마치면서

앞서서 열심히 공부하고 적었던 내용이 바뀌고, 또 어떤 내용은 아예 통채로 날아가 버리니 아주 상쾌하네요~! 하하하-_- 그런데 바뀐 내용을 보니깐, 훨씬 간단하고 깔끔해지고, 더 이해하기 쉬워진 것 같아서 좋네요. 그럼 오늘은 여귀까쥐!


- 참고자료

1. http://blogs.msdn.com/cburrows/archive/2010/04/01/errata-dynamic-conversions-and-overload-resolution.aspx

저작자 표시 비영리 변경 금지
신고
- 뭔소리여

Eric Lippert가 만우절에 쓴 글에 아주 제대로 낚였습니다. C# 4.0이 정식으로 나오기 직전인데, 급하게 추가된 연산자가 있다더군요. 자바에 얼마전에 이런 기능이 추가되어서, C#의 우위를 유지하기 위해서 급하게 추가해서 가장 최근버전의 CTP버전에서 확인할 수 있다는 말이었습니다. 추가된 연산자는 '-->'랑 '<--'인데, 전자는  '~로 향해가는'이라는 의미이고, 후자는 '~로 접근하는'이라는 의미라고 합니다. 무슨 말인고 하니....

int x = 10;
// this is read "while x goes to zero"
while (x --> 0)
{
    Console.WriteLine("x = {0}", x);
}

위의 코드에서 while문안의 식은 x가 0에 도달할때까지라는 의미를 갖습니다.

int x = 10;
// this is read "while zero is approached by x"
while (0 <-- x)
{
    Console.WriteLine("x = {0}", x);
}

위의 코드에서는 0이 x에 의해서 접근되어질때까지(즉, x가 0으로 접근할 때까지)의 의미를 갖습니다.


제가 귀가 얇아서 일까요? 순간 '혹'해서, '좋은데? 낄낄낄'하고 생각하면서 글을 읽어나갈 무렵, 마지막 줄이 눈에 들어왔습니다. '만우절인데 얼레리꼴레리 속았지? 속았지? 우헤헤헤헤헤헤'. 저는 낚인줄도 모르고 신나게 파닥파닥 거린셈이죠. 호호호호호호. 글을 보니, C# QA팀의 테스터한명도 낚여서 파닥거렸다고 합니다. 그리고 참고로, '-->'이 연산자는 몇년 전부터 돌아다니는 쫌된 유머라고 하네요. 즉, 'x-- > 0'은 'x --> 0'과 동일하다는 거죠..... 아흙.

namespace Console1
{
 class Program
 {
  public static void Main(string[] args)
  {
   int x = 10;
   
   while(x --> 0)
   {
    Console.WriteLine("{0}",x);
   }
   
   Console.Read();
  }
 }
}

위의 코드는 아래와 같은 결과가 나오고, while문 안의 조건을 'x-- > 0'으로 바꿔도 결과는 동일합니다.




- 마치면서


사실 저도 이글을 가지고 여러분을 낚아볼려는 생각을 가지고 글을 쓰기 시작했는데, 그랬다가 많은 분들의 호응(?)을 받아서 교훈을 얻을까봐, 소개해드리는 정도로 마쳤습니다. 제가 참 좋아하는 블로거이고, 또 배우러 자주가는 블로그에서 낚일줄은 생각도 못했네요-_- 


- 참고자료

1. http://blogs.msdn.com/ericlippert/archive/2010/04/01/SomeLastMinuteFeatures.aspx
신고

Welcome to Dynamic C#(13) - 아직도 가야할 길.

C# 2010.01.20 09:00 Posted by 뎡바1
- 제목이 표절인거 같은데...?

넵. 존경해 마지 않는 스캇 펙의 아직도 가야할 길을 요즘 감명깊게 읽고 있기 때문만은 아니구열. dynamic키워드로 아직도 써야 할 내용이 남아 있기 때문에, 한번 써봤습니당. 역시, 프로그래밍 언어의 현대적인 패러다임을 따라잡는 건, 단순히 사용하는 패턴만 익히는 게 아니라는 걸 다시한번 깨닫게 되네요. 그럼그럼~ 계속해서 한번 가보시져!


- 프로퍼티

d.Foo를 예로들면, d는 dynamic객체이고, Foo는 d속에 살고 있는 멤버 변수나 프로퍼티입니다. 컴파일러가 이런 구문을 만나면, 우선 Foo라는 이름을 payload속에다가 기록합니다. 그리고 런타임에게 d의 실제 타입을 찾아서 연결(바인드)해달라고 요청합니다.
payload : 캡슐화를 통해서 제공되는 컴퓨터 프로그램이나, 데이터 스트림속에서 사용자의 정보등을 나타내는 부분(출처 : http://en.wikipedia.org/wiki/Payload). 여기서는 C# 런타임 바인더가 해당 구문을 제대로 바인드하기 위해서 필요한 정보를 기록해 놓는 데이터 구조를 뜻합니당.

그리고 이런 프로퍼티는 항상 3가지경우 중 한가지경우에서 쓰이는데요. 값을 읽어오거나, 값을 대입하거나, 둘다 하거나(+=같이). 컴파일러가 사용된 모양을 보고, 어떻게 사용하려고 하는지도 payload에 같이 기록합니다. 즉, 읽기만 하는 경우에는 해당 프로퍼티는 읽기전용으로 기록을 하는 식으로 말이죠.

그리고 컴파일러는 이런 접근이 필드에 접근하는건지, 프로퍼티에 접근하는건지 딱히 구분하지 않습니다. 그건 나중에, 런타임이 구분을 하게됩니다. 그리고 컴파일할때, 이런 구문의 리턴 타입은 dynamic으로 설정됩니다.


- 인덱서

인덱서는 두가지로 생각해볼 수 있습니다. 첫번째는 매개변수가 있는 프로퍼티, 두번째는 배열이나 리스트같은 집합의 이름을 통한 메서드 호출. dynamic과 연관지어서 생각할때는 후자가 훨씬 도움이 됩니다. 메서드의 경우와 같이 인덱서도 정적으로 바운드 될 수 있지만, dynamic타입의 매개변수가 주어지고, 그 매개변수가 dynamic타입을 받는 인덱서로 정적 바운드가 되지 않는 경우, 오버로드 판별 과정에 유령이 끼어들게 됩니다. 그래서 인덱서의 수신자(receiver)는 정적타입이지만, 매개변수가 dynamic타입이라서 런타임에 늦은 바인딩이 일어나게 됩니다. 말로 설명하니깐, 깝깝하시죠? 실력부족으로 더 이상 말로는 깔끔하게 설명을 못드리겠네요-_-;; 예제로 설명을 드리면요.

public class C
{
    public int this[int i]
    {
        get
        {
            return i;
        }
    }

    public int this[dynamic d]
    {
        get
        {
            return d;
        }
    }
   
    static void Main(string[] args)
    {
        C c = new C();
        Console.WriteLine(c[5]);
        dynamic d = 7;
        Console.WriteLine(c[d]);
    }
}


위와 같은 코드를 보시면, C에 인덱서가 두개가 있습니다. 하나는 int를 매개변수로, 하나는 dynamic을 매개변수로 받죠. 그리고 Main메서드 안에서 하나는 int를, 하나는 dynamic타입의 매개변수를 넘겨주고 있습니다. 이 경우에 두번째 인덱서는 언제 어떻게 바인드될까요? 이경우는 비록 d가 타입이 dynamic이지만, 인덱서의 오버로드중에, 매개변수를 dynamic타입으로 받는 인덱서가 있습니다. 그래서 컴파일하는 시점에 "c[d]"이 인덱서 호출은 "public int this[dynamic d]"이 인덱서로 바인드 됩니다. 정적바인드가 되는거죠.

그런데, 만약에 dynamic을 받는 오버로드가 없다고 한다면 어떻게 될까요? 인덱서 호출을 받는 수신자는 c이고 c의 타입은 정적 타입인 C입니다. 하지만, 매개변수가 dynamic이죠. 그런데, dynamic과 일치하는 오버로드가 없습니다. 그래서 이때, 지지난 포스트에서 설명드렸던, 유령이 끼어들게 되는거죠.

메서드 처럼 생각하는게 편하다는 말씀은 드렸지만, 사실 프로퍼티와 유사한 면도 있습니다. 인덱서호출 역시 payload에다가 읽기, 쓰기등을 어떻게 하는지 기록합니다. 그래서 C# 런타임 바인더가 그 정보를 바탕으로 바인드할 수 있도록 말이죠. 그리고 인덱서의 리턴타입 역시 컴파일하는 시점에서는 dynamic으로 간주됩니다.


- 형변환

지지난 포스트에서 설명을 드릴때, dynamic은 다른 타입으로 암시적 형변환은 안되지만 되는 경우가 있다고 설명을 드렸었습니다. 그리고 지난 포스트에서 사실 그런 형변환이 대입 형변환이라는 설명도 드렸구요~. 형변환의 경우는 payload가 매우 단순해집니다. 왜냐면, 컴파일러는 이미 어떤 타입으로 형변환을 하려고 하는지 알고 있기 때문이죠. 그래서 컴파일러는 그냥 payload에 형변환 하려고 하는 타입을 기록하고, 런타임 바인더에게 가능한 모든 대입 형변환(형변환 연산자를 쓰는 경우에는 명시적 형변환도 같이)을 시도해보라고 이야기 해줍니다. 물론, dynamic타입이 아니라, 런타임에 결정될 실제 타입에서 목표 타입으로 시도해보겠죠.

형변환의 경우는 다른 모든 경우와 다르게 컴파일하는 시점에서 dynamic이 아닌 형변환의 목표타입을 리턴합니다. 위에서 말씀드렸듯이 이미 어떤 타입으로 형변환하려고 하는지 알 수 있게 때문이죠.


- 연산자

연산자는 초큼 특이합니다. 그냥 아무생각없이 훑어보면, 동적인 뭔가가 일어난다고 느끼기 힘들기 때문이죠. 그런데, d+1 같은 간단한 구문도 런타임에 바인드 되어야 합니다. 그 이유는 사용자정의 연산자가 끼어들 수 있기 때문입니다. 그래서, dynamic 매개변수를 갖는 모든 연산은 런타임에 바인드됩니다. +=나 -=같은 연산자도 포함해서 말이죠.

컴파일러는 연산자를 보면, d.Foo += 10 같이 멤버에 대입하는 연산이 있는지 혹은, d += 10 같이 변수에 대입하는 연산이 있는지 확인합니다. 그리고 그 과정에서 d를 ref를 통해 넘겨서 변경된 값이 유지되어야 하는지 확인합니다.

그리고 마지막으로 d.Foo += x 같은 구문이 있을 때, d.Foo가 바인드결과 delegate나 event타입이라면, 앞의 구문은 이벤트 수신자 추가 같은 적절한 메서드를 호출하도록 컴파일러가 연결해줍니다.


- 델리게이트 호출

데일게이트 호출은 메서드와 굉장히 유사합니다. 딱 한가지 틀린 점이 있다면, 호출되는 메서드의 이름이 명시되지 않는다는 것 뿐이죠. 그래서, 아래 예제의 두 호출은 모두 런타임에 바인드됩니다.

public class C
{
    static void Main(string[] args)
    {
        MyDel c = new MyDel();
        dynamic d = new MyDel();

        d();
        c(d);
    }
}


첫번째 호출은 매개변수가 없는 호출을 런타임에 바인드하게 됩니다. 런타임 바인더가 런타임에 호출의 수신자가 델리게이트 타입이 맞는지 확인하고 해당 델리게이트 시그니처와 일치하는 호출이 있는지 오버로드 판별을 통해서 찾게 됩니다.

두번째 호출은 매개변수가 dynamic타입이기 때문에, 런타임에 바인드됩니다. 컴파일러가 컴파일시점에서 c의 타입이 델리게이트라는 걸 확인할 수 있지만, 실제 오버로드 판별은 런타임에 가서 끝나게 됩니다.


- 마치면서

이제야 저는 dynamic에 대한 내용들이 머리속에서 아주 조금 자리를 잡은 듯한 느낌이네요. 저도 이런데 혼란스러웠던 지난 포스트를 보신 분들은 더 하시겠죠-_- 최대한! 최대한! 앞으로도 열심히 적겠습니다. 그럼 다음포스트에서 뵙죠~.

- 참고자료

1. http://blogs.msdn.com/samng/archive/2008/12/11/dynamic-in-c-v-indexers-operators-and-more.aspx
신고

- 뭐시 외로운 아이여? 스타아닌겨?

네. 확실히 C# 4.0의 가장 큰 키워드는 Dynamic이기 때문에, dynamic은 스타일지도 모르겠습니다. 하지만, 화려한 모습뒤에 감쳐진 그들의 일상사는 때때로 자살같은 비극적인 사건을 통해서 세간에 알려지곤 하죠. 그럴때마다 세삼스럽게 사람들은 화려한 일상뒤의 모습은 변비때문에 우울해하는 것 같이, 보통사람과 전혀 다르지 않음을 재확인 합니다..... 왜 이런 헛소리를 또 하고 있을까요-_-;;; 아무튼. dynamic은 초큼 외로운 아이입니다. 증거를 제시해드리죠.



그림1. 출처 : http://blogs.msdn.com/cburrows/archive/2008/11/06/c-dynamic-part-iv.aspx

네. 다른 타입들은 System.Object로 부터 아주 사이좋게 이리저리 연결되어 있습니다만, dynamic은 천상천하유아독존입니다. 그저 혼자 있을 뿐이지요. 어린이집에서도 유별난 애들은 꼭 걔네들 기분에 잘 맞춰줘야 해서 선생님들이 고생을 하기도 하는데요. dynamic역시 독특한 면을 갖고 있습니다. 지난 포스트에 이어서 dynamic의 형변환 룰에 대해서 알아보면 아래와 같습니다.

1. dynamic에서 dynamic으로 동일한 형변환이 가능
2. 모든 참조형 타입에서 dynamic으로 암시적인 참조형변환이 가능
3. 모든 값형 타입에서 dynamic으로 암시적인 박싱형변환이 가능
4. dynamic에서 모든 참조형 타입으로 명시적인 참조형변환이 가능
5. dynamic에서 모든 값형 타입으로 명시적인 언박싱 형변환이 가능
리스트1. dynamic에서 다른 타입으로 형변환 가능여부.

  아래부분에 줄로 그어 버린 부분은 정식버전이 출시되면서 개념이 바뀐 부분들입니다. 그리고 밑에서 설명드리는 '대입형변환'이라는 용어도 여전히 의미가 있는지 불분명합니다. 다만, C# 4.0 명세서에 보면 '대입형변환'이라는 개념이 없는 걸로 봐서는 설명을 위해서 도입한 개념이 아닌가 싶은데요, 이에 관해서 Chris Burrows에게 질문을 남겨놨는데요, 답이 오면 바로 업데이트 하겠습니다. 바뀐 내용에 대해서는 이 글을 참조하시기 바랍니다.

매우 직관적으로 보이긴 하지만, 좀 생각해보면 이상한 점들이 발견됩니다. 그 첫번째가 바로 dynamic에서 object로 암시적인 형변환이 없다는 사실인데요. 위에서 1, 4번에서 언급했듯이 dynamic에서 dynamic을 제외한 모든 참조형타입으로 암시적인 형변환이 없다고 하고 있습니다. 그 이유는 연산을 하는 도중에 둘을 구분해내기가 매우 어렵기 때문이라고 합니다. 근데, 아래와 같은 코드가 컴파일 되고 실행되는 걸 확인할 수 있습니다.

dynamic d = null;
object o = d;

이건 분명히 암시적 형변환 처럼 보이는데, 왜 이게 컴파일이 되는 걸까요? 사실, 두번째줄은 암시적 형변환이 아니라 대입 형변환입니다. 대입 형변환은 또 뭘까요?

그림 2. 출처 : http://blogs.msdn.com/cburrows/archive/2008/11/11/c-dynamic-part-v.aspx

위 그림을 보시면, 형변환이 총 3가지로 분류가 되는 걸 확인하실 수 있습니다. 대입 형변환은 명시적 형변환과 암시적 형변환의 사이에 위치해 있는데요. 모든 대입 형변환은 사실 명시적 형변환이며, 모든 암시적 형변환은 대입 형변환 인거죠. 왜 이런 걸 새로 도입했어야 할까요? 사실 C# 4.0작업을 하면서 dynamic에서 다른 타입으로 암시적 형변환 도입을 검토했었다고 합니다. 그런데, 이렇게 하게 되면 문제가 생기는데요. 바로, dynamic을 통해서 아무타입에서 아무타입으로 형변환이 가능하기 때문입니다. 이 문제를 오버로드 판별을 예로 들어서 설명해보겠습니다.

public class C
{
    public static void M(int i){}

    public static void M(string s){}
   
    static void Main(string[] args)
    {
        dynamic d = GetSomeDynamic();
        C.M(d);
    }
}

코드1. 만약 암시적 형변환이 가능하다면?

위와 같은 코드가 있다고 할때요, 과연 어떤 메서드가 실행되어야 할까요? dynamic에서 모든 타입으로 암시적 형변환이 있다면, dynamic에서 int도 dynamic에서 string도 가능한 상황이 됩니다. 물론, dynamic에서 object같이 dynamic에서 int보다 더 나은 걸 찾을 수도 있겠지만, 그렇지 않은 경우가 훨씬 많이 발생하게 됩니다. 이런 모호함 때문에 dynamic에서 다른 타입으로 형변환을 할때는 명시적으로 선언을 하게 제한을 둔 거죠.

그렇다면, 대입 형변환은 또 뭘까요?

1. dynamic에서 모든 참조형 타입으로 대입 참조 형변환이 가능
2. dynamic에서 모든 값형 타입으로 대입 언박싱 형변환이 가능
리스트2. 대입 형변환의 설명

[리스트1]에서 4,5번을 보면 명시적 형변환에 대해서 이야기 하고 있죠? 사실은 그 명시적 형변환이 바로 이 대입 형변환을 말하는 겁니다. 모든 대입 형변환은 명시적 형변환이라고 말씀드렸던 걸 떠올리시면 고개가 절로 끄덕끄덕....교회 다니시는 분들은 교회로 끄덕끄덕 하실겁니다. 그리고 리스트2의 모든 형변환과 암시적 형변환을 모두 합하면 바로 대입 형변환이 되는 거죠.


- 그래그래 어디 계속 해봐.

대입이 일어나는 곳이 바로 컴파일러가 대입 형변환을 시전하는 곳입니다.

dynamic d = GetSomeDynamic();
Worksheet ws = d; //대입 형변환

이거 말고도, 대입 비스무리한 것들은 모두 대입 형변환을 사용합니다. return과 yield 그리고 프로퍼티와 인덱서, 배열 초기화구문, 그리고 foreach나 using같은 구문말이죠.

return d; //return할 타입으로 대입 형변환
yield return d; //반복자의 타입으로 대입 형변환
foo.Prop; //Prop의 타입으로 대입 형변환
foo[1] = d; //인덱서의 타입으로 대입 형변환
bool[] ba = new bool[] { true, d }; //bool로 대입 형변환
foreach(var x in d) {} //IEnumerable로 대입 형변환
using (d) {} //IDisposable로 대입 형변환
리스트3. 대입 형변환이 어디어디서 끼어드는지!

하지만, 이런 대입 형변환을 사용하지 않는 곳 중에 하나가 오버로드 판별입니다. [코드1]에서 보셨듯이 만약에 메서드를 호출하는데 대입 형변환을 적용하게 되면, 매번 메서드를 호출할때마다 모호함때문에 캐고생을 하게 될겁니다. 하지만, 대입 형변환을 적용하지 않는다고 해도, [코드1]은 제대로 컴파일 되지 않을거 같습니다. 왜냐면, d의 타입인 dynamic에서 C의 두 오버로드가 받는 파라미터 타입인 int와 string으로 형변환이 불가능 하기 때문입니다. 하지만, 이런 코드가 컴파일 되고 잘 돌아가야만 하니깐, 바로 지난 포스트에서 언급했던 유령 메서드가 끼어들게 되는거죠.


- 마치면서

어찌 퍼즐 조각이 좀 맞아 들어가시나요? 저도 글을 쓰면서 다시한번 자세히 읽다보니 퍼즐조각이 조금씩 맞아들어가는 느낌이 드는데요. 꼭 무슨 그것이 알고싶다에서 사건 조사하는 거 같은 기분이네요-_-. 그럼 다음 포스트에서 뵙져!!!


- 참고자료

1. http://blogs.msdn.com/cburrows/archive/2008/11/06/c-dynamic-part-iv.aspx
2. http://blogs.msdn.com/cburrows/archive/2008/11/11/c-dynamic-part-v.aspx

신고

Welcome to Dynamic C#(11) - The Phantom of The Dynamic

C# 2010.01.14 09:00 Posted by 뎡바1
  이 포스트에서 소개해드리는 유령메서드는 정식버전에서는 없어진 개념입니다. 너무나도 복잡한 과정을 거쳐야 하기 때문에, 과정을 단순화 시키느라고 기존에 논의하던 개념들을 채택하지 않았다고 하는데요. 이 포스트는 예전에 이런 내용이 논의되었었다하는 정도로 봐주시구요. 실제 dynamic과 관계된 메서드 오버로딩 판별에 대해서는 이 글을 참조해주시기 바랍니다.

- 빙산의 일각!


사실 처음에는 그냥 단순히.. dynamic이란게 추가됐으니, '아 이거 쵸낸 신기하구나', '아 난 귀찮은 거 싫어하는데 이거 때매 이런거 편해지겠구나'하는 생각으로 접근을 했었는데요. dynamic이라는 키워드가 하나 추가되면서 생긴 많은 양의 추가사항들과 이야기들이 빙산 아래쪽으로 숨어 있었네요. 근데, 개인적인 게으름으로 아직 제대로 이야기 드리지 못하는거에 대해서 죄송하게 생각하구요-_- 일단, 원문을 한번 걸러서 약간의 창작을 보태는 수준에 불과하지만 계속해서 최선을 다해서 전달해드리고자 합니다.(저도 궁금하긴 하거든요-_-) 따쓰한 피드백!! 크하하하-_-


- 유령...뭐...?

유령 메서드(the phantom method)는 이 포스트시리즈의 바탕이 되는 Sam Ng의 포스트에서 언급이 되는 용어인데요. 컴파일러가 초기 바인딩단계에서 정적으로 해결할 수 없는 동적 바인딩을 해야하는 경우 사용하는 메서드를 말합니다. 즉, 실체는 없지만 컴파일러 내부에서 문제해결을 위해서 사용한다고 볼 수 있겠죠.

public class C
{
    public void Foo(int x)
    {
    }
   
    static void Main(string[] args)
    {
        dynamic d = 10;
        C c = new C();
        c.Foo(d);
    }
}

위와 같은 코드가 있다고 할때요, Foo의 호출을 바인드하기 위해서 컴파일러는 오버로드 판별 알고리즘을 통해서 C.Foo(int x)같이 딱들어맞는 후보메서드가 포함되어 있을 후보군을 만듭니다. 그리고 매개변수가 변환가능한지를 판단합니다. 그런데, 아직 dynamic의 변환가능성에 대해서는 이야기를 해본적이 없으므로~ 일단, 그거에 대해서 먼저 이야기 해보도록 하겠습니다.

우선, 빠르고 간단하게 정의를 내려보자면, "모든 타입은 dynamic으로 변환이 가능하고, dynamic은 어떤 타입으로도 형변환 할 수 없습니다." 말이 안된다고 생각하실 수 있습니다. 그럼 그 의문을 풀기 위해서 형변환을 고려할 수 있는 상황들을 생각해보기로 하죠.
(주석) 원문에서 "everything is convertible to dynamic, and dynamic is not convertible to anything"이라고 하고 있는데요, 조금 알아보니 Sam Ng가 이야기한 건 아마 암시적 형변환을 두고 이야기 한 것 같습니다. dynamic에서 다른 타입으로 명시적 형변환은 가능합니다. 그리고 이에 대해서 다음 포스트에서 좀 더 자세하게 말씀 드리겠습니다. 

우선, c를 정적인 타입이라고 하고, d를 동적인 타입의 표현식이라고 했을때요, 형변환에 대해서 고려해볼 수 있는 상황은 아래와 같습니다.

1. 오버로드 판별 - c.Foo(d)
2. 대입 형변환 - C c = d
3. 조건문 - if(d)
4. using절 - using(dynamic d = ..)
5. foreach - foreach(var c in d)

일단, 이번 포스트에서는 1번에 대해서 살펴보도록 하구요, 나머지는 다음기회로 미루겠습니다.


- 오버로드 판별의 경우

일단 매개변수 변환가능성으로 돌아가서 생각해보겠습니다. dynamic은 어떤 타입으로도 형변환 할 수 없기 때문에, d는 int로 형변환이 불가능합니다. 하지만, 모처럼 dynamic타입인 매개변수를 썼으니 오버로드 판별도 동적으로 일어났으면 합니다. 그래서 여기서 유령 메서드가 등장합니다.

유령메서드는 오버로드 판별의 후보군에 슬쩍 추가되는데요, 파라미터개수가 똑같고 모든 파라미터가 dynamic타입인 메서드입니다.

즉, 후보군에 Foo(int x, int y), Foo(string x, string y)가 있다면, 유령메서드는 Foo(dynamic x, dynamic y)처럼 생겼겠죠.

유령 메서드가 후보군에 끼어들게되면, 당연하겠지만, 다른 후보군 메서드와 똑같이 취급됩니다. '모든 타입은 dynamic으로 형변환이 되지만, dynamic은 어떤 타입으로도 형변환이 안된다.'는 명제를 다시 한번 떠올려 볼 때입니다. 아주 적절한 타이밍이죠. 이 말은 dynamic타입의 매개변수가 주어진 상황에서 다른 모든 오버로드 후보군은 판별을 통과하지 못하지만, 유령 메서드는 유유히 판별을 통과할 수 있다는 말입니다.

서두에서 제시했던 예제를 다시 보시면, 한개의 dynamic타입을 매개변수로 받습니다. 이 상황에서 오버로드는 두개인거죠. Foo(int)와 유령 메서드인 Foo(dynamic). 전자는 판별을 통과하지 못합니다. dynamic은 int로 형변환이 안되기 때문이죠. 하지만 후자는 성공합니다. 그래서 그 메서드에 호출을 바인드하게 되는거죠.

그리고 호출이 유령 메서드에 바인드 되면, 컴파일러는 DLR을 통해서 런타임에 호출이 제대로 이루어 지도록 조치를 취하는 거죠.

그럼, 한가지 의문이 남는데요. 유령 메서드는 언제 끼어들게 되는 걸까요?


- 유령이 끼어드는 그 순간

컴파일러가 오버로드 판별을 할때, 초기 후보군을 놓고 고심을 합니다. 메서드 호출에 dynamic 매개변수가 있다면, 컴파일러는 후보군을 찬찬히 살펴보면서 유령 메서드를 소환해야 하는지 고심합니다. 유령 메서드가 끼어드는 상황은 아래와 같습니다.

1. dynamic이 아닌 모든 매개변수는 후보군에 있는 파라미터로 형변환이 가능하다.
2. 최소한 한개 이상의 dynamic매개변수가 후보군에 있는 파라미터로 형변환이 불가능하다.

일전에, dynamic타입인 매개변수가 포함된 메서드 호출이라도 정적으로 바인드될 수 있다고 말씀을 드렸었는데요. 이 경우가 2번으로 설명이 됩니다. dynamic매개변수와 일치하는 dynamic파라미터를 갖는 후보메서드가 있다면, 호출은 그 메서드로 정적 바인딩이 됩니다.

public class C
{
    public void Foo(int x, dynamic y)
    {
    }
   
    static void Main(string[] args)
    {
        C c = new C();
        dynamic d = 10;           
        c.Foo(10, d);
    }
}

위와 같은 경우, 오버로드 후보군에 정확하게 Foo호출의 매개변수인 int와 dynamic타입에 일치하는 메서드가 있기 때문에, 정적으로 바인딩되고, dynamic lookup이 발생하지 않습니다. 즉, 오버로드 판별시에 후보군을 한번 쭉 훑었는데도 유령 메서드가 끼어들지 않았다면, 비록 dynamic타입의 매개변수가 있다고 하더라도 원래 하던대로 오버로드 판별을 진행하는 거죠.


- 유령메서드를 통한 할당과 dynamic receiver를 통한 할당은 뭐가 틀린거냐

dynamic receiver의 경우는 런타임 바인더가 실제 런타임시의 receiver의 타입을 기준으로 오버로드 판별을 하려고 합니다. 하지만 유령메서드가 끼어든 할당의 경우는 컴파일타임의 receiver의 타입을 기준으로 진행되는 거죠.

그냥 직관적으로 생각해봤을때, receiver를 컴파일타임에 알 수 있다면, 매개변수에 dynamic타입이 있다고 하더라도 오버로드 판별의 후보군은 컴파일 타임에 밝혀져야 하는 거겠죠.


- 마치면서

사실, 이 부분에 대해서 좀 더 언급할 사항들이 있습니다. 나머지 형변환 케이스에 대해서 더 나아가기 전에, dynamic의 형변환에 대한 부분과 오버로드에 대한 이야기를 좀 더 하려고 하는데요. 여러분과 제가 가진 의문이 조금씩 더 해결됐으면 하는 생각입니다.


- 참고자료

1. http://blogs.msdn.com/samng/archive/2008/11/09/dynamic-in-c-iv-the-phantom-method.aspx


신고

Welcome to Dynamic C#(10) - Dynamic Returns Again.(2)

C# 2010.01.11 09:00 Posted by 뎡바1

- 살림살이 좀 나아시졌습니까.

날씨가 계속 춥네요. 난데없이 목이 부어서 지난 금요일에는 조퇴를 했습니다-_-. 다들 건강 조심하시구요. 날씨가 추워서 그런가 여기저기서 어려움을 호소하는 목소리가 들리는 거 같습니다. 다들 하시는 일 잘되고 살림살이 좀 나아지셨으면 좋겠네여!


- 이어서 이어서!

지난포스트에서 보셨던 예제를 다시한번 보시죠.

dynamic d = 10;
C c = new C();

d.foo();
d.SomeProp = 10;
d[10] = 10;

c.Foo(d);
C.StaticMethod(d);
c.SomeProp = d;

지난 포스트에서는 위쪽 그룹에 대해서 다뤘었는데요, 이번 포스트에서는 위 두 그룹중에서 아래쪽 그룹에 대해서 설명드려 보겠습니다. 우선, 가장 간단한 부분부터 다뤄보려고 하는데요, 아래와 같은 코드가 있다고 가정해보죠.

public class C
{
    public void Foo(decimal x) { ... }
    public void Foo(string x) { ... }
    static void Main(string[] args)
    {
        C c = new C();
        dynamic d = 10;
        c.Foo(d);
    }
}

이 코드가 실행되면 어떤일이 벌어질까요? 직관적으로 생각해봤을때... 지역변수c의 타입이 C라는 걸 알 수 있으니깐, C의 오버로드 2개중에 하나가 실행될거라고 생각해볼 수 있습니다. 근데, d의 타입이 dynamic이라는 것도 위 소스코드에서 알 수 있는데요, 그렇다는 이야기는 d의 실제 타입은 런타임에서 알 수 있으므로 컴파일러는 어떤 오버로드를 호출해야 하는지 판단할 수가 없습니다.

그래서 이렇게 생각해볼 수 있습니다. 컴파일러는 호출가능한 후보군을 추출해서 집합을 만들고, 런타임에 실제로 오버로드 판별을 통해서 적합한 메서드를 호출한다고 말이죠. 위의 경우에는 d의 값이 10이므로, 아마도 호환이 안되는 string보다는 decimal을 인자로 받는 오버로드가 호출될거라고 예측해볼 수 있습니다. 그러면, 좀 더 구체적으로 이야기 해보면서 뭐가 예측이랑 다르게 돌아가는지에 대해서 이야기 해보죠.

public class C
{
    public void Foo(decimal x) { ... }
    public void Foo(string x) { ... }
    static void Main(string[] args)
    {
        C c = new D();
        dynamic d = 10;
        c.Foo(d);
    }
}

public class D : C
{
    public void Foo(int x) {...}
}


클래스 D를 C로 부터 상속했고, c를 D의 생성자로 생성한 게 차이점인데요. 그리고 런타임에 c의 타입은 D일텐고, D에는 C가 가진 모든 오버로드보다 더 이 경우에 적합한 int형 파라미터를 가진 Foo가 있습니다. 10은 int형으로 간주될테니, D의 Foo가 가장 적합한 선택이 되겠죠.

그런데, 결과는 어떨까요? 이 부분에 대해서 잘 아시는 분이나 코드를 작성하다가 이상한 점을 발견하신 분이라면 대답하실 수 있으실텐데요. 아래의 그림에서 확인을..


네. 분명히 오버로드가 총 3개여야 할텐데, 두개 밖에 안 나옵니다. 그래서 분명히 D의 Foo가 가장 좋은 선택임에도 불구하고 계속해서 C의 Foo(decimal x)가 호출이 됩니다. 하지만, c를 생성할때 "D c = new D();"나 "dynamic c = new D()"처럼 생성하면, D의 Foo(int x)가 호출이 됩니다. 왜 그럴까요?


- 내가 알면 이글 보고 있겠냐.

dynamic과 관련된 동적 바인딩을 설계할때, 최대한 동적바인딩이 기존의 컴파일러가 정적바인딩에서 하던 짓을 비슷하게 하게끔 유지했다고 합니다. 그래서 그 결과로 dynamic이라고 명시된 매개변수나 receiver가 아니라면, 컴파일타임에 확인할 수 있는 타입을 그 변수의 타입으로 간주한다고 합니다. 즉, 위의 예제에서 c.Foo의 오버로드로 D.Foo(int x)가 포함이 안된 이유는, "C c = new D();"의 결과로 c가 런타임에 가질 타입은 D겠지만, 컴파일타임에서는 C라고 간주한다는 겁니다. 그래서 아무리 인텔리센스를 뒤져봐도 D의 Foo를 발견할 수 없으며 호출도 할 수 없는 거죠.

하지만, "D c = new D();"나 "dynamic c = new D()"처럼 생성하면, 전자의 경우는 컴파일 타임의 c의 타입을 D로 간주하므로 오버로드 3개모두를 인텔리센스에서 확인하실 수 있구요, 후자의 경우는 동적 바인딩을 통해서 c의 런타임 타입이 D임을 알기때문에, 오버로드 후보군에서 Foo(int x)를 골라낼 수 있습니다. 예제를 하나 더 보면요.

public class C
{
    public void Foo(int x, object o)
    {
        Console.WriteLine("Foo(int x, object o)");
    }

    static void Main(string[] args)
    {
        C c = new D();
        dynamic d = 10;
        c.Foo(d, c);
    }
}

public class D : C
{
    public void Foo(int x, D d)
    {
        Console.WriteLine("int x, D d");
    }
}

위의 경우와 마찬가지로, C.Foo(int x, object o)가 호출됩니다. 같은 이유로 말이죠.


- 마치면서

사실 이번 포스트는 좀 애로사항이 있었는데요. 제 실력부족으로 참고했던 원문이 잘 이해가 안가서 말이죠. 그래서 제 나름대로 이런저런 실험을 해보다가 결론을 내렸습니다. 바로 이럴때가 고수님들의 나눔이 필요한 시점입니다. 혹시 더 자세하게 아시는 분이 있다면, 따쓰한 피드백으로 풍성하게 해주시기 바랍니다. "따쓰한" 잊지마세염^^;;;


-참고자료

1. http://blogs.msdn.com/samng/archive/2008/11/06/dynamic-in-c-iii-a-slight-twist.aspx


신고

Welcome to Dynamic C#(9) - Dynamic Returns Again.

C# 2010.01.07 09:00 Posted by 뎡바1

- 정말 오랜만에 다시 Dynamic이군요.

안녕하세요~! 눈 때문에, 어떤 사람들은 로맨틱한 겨울이고, 어떤 사람들은 악마의 똥가루의 냄새에 신음하고, 어떤 사람들은 방에 콕처박혀 있고 뭐 아주 버라이어티한 겨울입니다. 겨울이 버라이어티 정신이 충만하네요. 연예대상같은거라도 하나 받고 싶은 가봐요. ㅋㅋ 아무튼! 정말 오랜만에 다시 dynamic시리즈를 쓰게 되네요. 워낙 한 내용도 없이 중간에 끊어서 좀 그랬습니다;;; 물론, 기다리신 분이 얼마나 있을지는 미지수지만요-_- 그럼. 한번 이야기를 시작해볼까요? dynamic에 대해서 조금씩 자세하게 들어가 보겠습니다.


- 어서내놔 dynamic

우선 예제를 하나 보시죠.

dynamic d = 10;
C c = new C();

//위쪽 그룹 
d.foo();
d.SomeProp = 10;
d[10] = 10;

//아래쪽 그룹 
c.Foo(d);
C.StaticMethod(d);
c.SomeProp = d;




위 그룹과 아래 그룹의 차이점은 뭘까요? 네~! Give that man a cigar!(누가 정답을 말했을때 하는 말이라네요) 위 그룹은 액션을 받는 객체가 동적인 객체, 즉 dynamic receiver이구요. 아래 그룹은 static receiver와 static method가 바로 차이점입니다.

위 그룹은 동적인 표현식(expression)속에서 직접적으로 동적인 행위가 일어나고, 아래그룹은 직접적으로  동적표현식은 아닙니다. 각각의 연산의 매개변수로 동적인 타입이 들어가면서, 전체적인 표현식을 간접적으로 동적으로 만들고 있는거죠. 이런 경우에는 컴파일러가 동적인 바인딩과 정적인 바인딩을 섞어서 수행하는데요. 예를 들어서 동적타입을 매개변수로 받는 오버로드가 있을 경우에, 어떤 멤버집합(member set)을 오버로드해야 할지 결정할때는 정적인 타입을 사용해서 판단할테구요, 실제로 오버로드를 판별(resolution)할때는 매개변수의 런타임 타입을 사용할 것이기 때문이죠.

컴파일러가 dynamic타입인 표현식을 보게되면, 그 안에 포함된 연산들을 동적 연산처럼 처리하게 됩니다. 즉, 표현식이 인덱스를 통한 접근이든 메서드호출이든 상관없이 그 표현식의 결과로 나오는 타입은 런타임에 결정될거라는 거죠. 그 결과로 컴파일 타임에 동적인 표현식의 결과로 나오는 타입은 dynamic이겠죠.

컴파일러는 이런 모든 동적인 연산들을 DLR을 통해서 dynamic call site라는 걸로 변환을 합니다. 지지지난 포스트에서 설명을 드렸던거 같은데요, 제네릭한 델리게이트를 가지고 있는 정적 필드입니다. 어떤 연산에 대한 호출을 가지고 있다가, 추후에 같은 타입의 연산이 호출되면 다시 call site를 생성할 필요없이 정적필드에 저장된 델리게이트를 호출해서 실행에 필요한 부하를 최대한 줄이는데 도움을 주는 친구죠. call site가 만들어지면, 컴파일러는 그 call site에 저장된 델리게이트를 호출할 코드를 생성하구요, 거기에다가 매개변수를 넘겨줍니다.

만약에, 호출한 객체가 IDynamicObject를 구현해서 스스로 동적 연산을 어떻게 처리할지 아는 객체가 아니거나, 미리 저장된 델리게이트와 타입이 안맞아서 캐시가 불발이 나면, call site와 같이 생성된 CallSiteBinder가 호출됩니다. CallSiteBinder는 call site에 필요한 바인딩을 어떻게 처리해야 하는지 알고 있는 객체인데요, C#은 이 CallSiteBinder에서 상속한 바인더를 갖고 있습니다. 이 C# CallSiteBinder가 적절한 바인딩을 통해서 DLR의 call site가 갖고 있는 델리게이트에 저장될 내용을 expression tree형태로 만들어서 리턴합니다. 이 내용역시 전전, 전포스트에서 다뤘었쬬? 못봤다고 하시면!!!! 제가 절대 가만있을수는 없는 문제고! 링크를 드..드리겠습니다. 전포스트, 전전포스트. 친절하죠?-_-


- 캐시되는 과정은 어떠냥

공개된 문서를 통해 볼 수 있는 현재의 캐시 방식은 그냥 단순히 매개변수들의 타입이 일치하는지 검사하는겁니다.  만약에.. 이런 호출이 있다고 할때...

args0.M(arg1, arg2, ...);

그리고, 이전에 args0이 C라는 타입이며, 매개변수 arg1과 arg2가 모두 int인 호출이 있었다고 해보면요, 캐시를 체크하는 코드는 대략아래와 같습니다.

if (args0.GetType() == typeof(C) &&
    arg1.GetType() == typeof(int) &&
    arg2.GetType() == typeof(int) &&
    ...
    )
{
    //CallSiteBinder의 바인드 결과는 여기에 계속 통합되구요
}
    ......//캐시 검사는 좀 더 많을 수도 있구요
else
{
    //여기서 CallSiteBinder의 bind메서드를 호출하고, 캐시를 업데이트 합니다.
}

지금까지 간단하게 알아본 내용을 그래도 마무리 하려면, C# CallSiteBinder가 뭘 어떻게 하는지를 알면 되겠네요. 서두에 두그룹의 연산중에 위 그룹의 연산을 보면요, 메서드 호출, 속성 접근, 인덱서 호출등 3가지 연산이 있었죠. 일단 모든 연산은요 표준 C# runtime binder를 통해서 생성되고, C# runtime binder가 걔네들을 데이터 객체로 사용합니다. 그 데이터객체는 바운드되야할 액션을 설명하는데요, 그런 객체를 C# payload라고 부른다고 합니다. 

C# runtime binder는 쉽게 작은 컴파일러라고 생각하면 되는데요, 얘가 일반적인 컴파일러가 갖고 있는 심볼테이블이나 타입시스템, 오버로드 판별 및 타입 교체같은 기능을 갖고 있기 때문입니다. 간단하게 d.Foo(1)을 예로 생각해보죠.

runtime binder가 호출되면, 현재 call site에 대한 payload과 call site에 대한 런타임 매개변수를 갖습니다. 그리고 dynamic receiver를 포함해서 그 모든 런타임 매개변수와 타입을 모아서는 그 타입에 대한 심볼테이블을 만듭니다.(심볼테이블에 대한 간략한 설명은 여기를 참조하세영!) 그리곤 payload꾸러미를 풀어헤쳐서 수행하려고 하는 연산의 이름을 꺼냅니다.(Foo) 그리고 d의 타입에서 리플렉션을 사용해서 Foo라는 이름을 갖는 모든 멤버를 뽑아냅니다. 그리고 걔네들도 심볼테이블에 적어넣죠. 말로 설명하니깐 깝깝하시죠? 설명하는 저도 깝깝하네여-_-;;; 제가 상상력을 동원해서 부연설명을 드리면요,

d.Foo(1)에서 먼저 매개변수의 타입과 d의 타입을 갖고와서 심볼테이블에 적어두고요.

주소     타입            이름
서울시   int             익명(= 1)

수원시   dynamic      d

그리고 리플렉션으로 d의 타입에서 Foo를 모두 찾아냈는데 대략 아래와 같다고 해보죠.
Foo(int a)
Foo(double b)
Foo(string c)

그리고 얘네들도 따로 심볼테이블에 집어넣으면?

-call site에 대한 심볼테이블
주소     타입            이름
서울시   int            익명(= 1)
수원시   dynamic      d

-d의 멤버중에 Foo라는 동명이인들
주소      타입        이름
부산시   void     Foo(int a)
창원시   void     Foo(double b)
안양시   void     Foo(string c)

그러면, 타입을 찬찬히 들여다보면, 어떤 Foo가 호출되야 할지 명확하게 보입니다. d.Foo(1)호출에서 매개변수의 런타임타입이 int이므로 Foo(int a)가 호출이 되겠죠. 이건 그냥 제가 설명을 위해서 상상력을 동원해본거니깐요 믿지는 마시기 바랍니다. 예비군 동원 무쟈게 귀찮으시져? 상상력도 무쟈게 귀찮아 하네요-_-. 어서 집에 보내고 다시 설명을 이어 가겠습니다.

위에서 설명드린 runtime binder를 설계할때 세웠던 한가지 원칙은 "runtime binder는 정적 컴파일러가 하는 짓을 똑같은 의미로 할 수 있어야 한다."였다고 하는데요. 그래서 에러메세지 역시 동일한 에러메세지를 뱉어낸다고 합니다.

위의 바인딩의 결과로 바인딩이 성공적일 경우에 수행할 동작을 표현한 expression tree가 만들어집니다. 그렇게 안되는 경우에는 runtime binder exception을 던진다고 하네요. 결과로 만들어진 expression tree는 DLR의 캐시에 포함되고 호출되면서 원래의 호출을 성공적으로 완료합니다.


- 약간의 제약사항?

그런데, 위에서 정적 컴파일러와 똑같은 짓을 하게 만들려고 했지만, 아마도 예산과 시간때문에 선택과 집중을 해야 하니깐 몇가지 못집어 넣은게 있다고 합니다. 람다식과 확장 메서드, 메서드 그룹(델리게이트)에 대한 이야기 인데요. 현재로서는 바인딩 안된 람다식을 런타임에서 표현할 방법이 없다고 합니다. 개발하다가 디버깅을 할때 브레이크 포인트를 잡고 그 상태에서 현재 상태의 객체에 값을 가져온다거나 메서드를 호출하고 값을 확인할 수 있잖아요? 근데, 람다식은 그런식으로 디버깅이 안됐던거 같은데, 아마 그문제가 계속 이어지는 거 같습니다.

그리고 메서드 그룹역시 런타임에 표현할 수 있는 방법이 없다고 합니다. 예를 들면,

delegate void D();
public class C
{
     static void Main(string[] agrs)
     {
        dynamic d = 10;
        D del = d.Foo; //뭐 이렇게는 안된다고 하네요. 그래서 런타임 익셉션이 난다고 합니다.
     }
}

그리고 확장 메서드 역시 using절과 범위를 바인딩없이 넘겨줄 방법이 없기때문에, 확장메서드역시 안된다고 하구요.


- 마물!

무척 오랜만의 포스팅인데요, 갈증이 조금이라도 해소가 되셨으면 좋겠네요. 제 실력이 바닥을 기다보니 원문의 내용을 한번 걸러서 드리는 정도밖에 못드리는 면이 많은데요. 뭐-_- 내공이 부족하니 한계가 명확하네요. 그럼 다음 포스트에서 뵙져~!!!!!!!


- 참고자료

1. http://blogs.msdn.com/samng/archive/2008/11/02/dynamic-in-c-ii-basics.aspx


신고

Welcome to dynamic C# 외전(2) - Generic Method.

C# 2009.11.30 08:30 Posted by 뎡바1

- 또 외전이군 ㅋ

오랜만의 포스팅이네요. 넵. 또 외전입니다-_-;;; 최근에 다른 일때문에 포스팅을 못하다가 어떤 계기가 생겨서 포스팅을 하게됐습니다. 음;; 제네릭 메서드를 사용해서 코딩할 일이 있었는데요, 갑자기 전에 어떻게 했는지 기억이 안나는거 있죠-_-. 그래서, 기억을 더듬고 검색해서 코딩을 했습니다. 그래서 저도 안까먹고, 혹시 이거때매 해매는 분들을 위해서 포스팅을 할까 합니다.

* 경고 : 늘 그렇지만 제가 하는 포스팅은 허접합니다. 왜냐면, 제가 허접하기 때문이죠. 포스팅의 허접함에 눈이 아프시다면, "Oh My Eyes!"를 한번 외치시고 뒤로가기 버튼을 활용하시면 직빵입니다. ㅋ OME는 스타에서만 나오는게 아니거등요.


- 제네릭?

넵. 제네릭은 불필요한 박싱/언박싱을 없애도록 도와준 고마운 기능입니다. C# 2.0에서 추가가 됐었죠. 내부적으로는 F#에서 유용하다고 검증이 돼서, C#에 추가하기로 결정을 했었다고 합니다. F#이 쫌 까칠하긴 해도, 파워풀 하거든요^^(까칠하다보니, F#이랑 쫌 잘지내보려고 데이트도 11번 정도 했지만 일단은 손 놨습니다. 상처받은 마음을 C#과 좀 달래려구요. 근데 요즘은 C#한테도 상처를....)

혹시 모르는 분들을 위해서 허접한 실력으로 부연설명을 드리자면요. 박싱(boxing)은 포장을 하는 거구요, 언박싱(unboxing)은 포장을 푸는겁니다. 즉, C#에는 크게 타입이 크게 두가지로 분류가 되는데요. 값형식(value type)과 참조형식(reference type)입니다. 값형식은 변수의 이름이 가리키는 곳에는 실제 값이 들어있고, 참조형식은 변수의 이름이 가리키는 곳에 실제값에 대한 참조가 있습니다. 즉, C에서의 포인터의 개념을 생각하시면 됩니다. 포인터는 실제값을 가지는게 아니라, 값이 있는 곳의 주소를 가지고 있는거니까요.

근데, 값형 타입이 닷넷의 최상위 타입인 object같은 참조형 타입과 같이 처리해야 되는 경우가 있습니다. 예를 들면, C# 1.0에서는 컬렉션이 object형만 받을 수 있었습니다. 그래서, int타입만 처리하는 경우에도 컬렉션을 사용하려면, object타입으로 int타입을 포장해서 집어넣고, 빼낼때도 object타입의 포장을 풀고 int타입을 꺼내와야 했던거죠. 그런데, 이게 너무 삽질이다 보니 더 편하게 할 수 있는 방법으로 제네릭이 등장 했습니다.

제네릭은 어떤 타입도 될 수 있는 걸 말하는데요, List<int>는 int타입만 받는 컬렉션, List<string>은 string타입만 받는 컬렉션이 되면서, 불필요한 박싱/언박싱도 없어지고 코드도 훨씬 명확해 지는 장점이 있습니다. 그런데, 꼭 이럴때 뿐만 아니라도 제네릭이 필요한 경우가 생기는데요. 오늘 제가 설명드리면서 보여드릴 예제가 그런 경우입니다.


- 그래그래. 예제 내놔봐.

예제는 XML로 객체를 시리얼라이즈(Serialize)하고 다시 디시리얼라이즈(Deserialize)하는 건데요. 시리얼라이즈을 할때는 시리얼라이징 하는 객체의 타입을 명시해줘야 합니다. 그래서 한 프로그램에서 여러객체를 시리얼라이즈해야 할때는, 그 수만큼 오버로딩이 되기도 합니다. 하지만, 제네릭이 출동한다면?

제.

네.

릭.

...죄송합니다. 스덕이다 보니, 스타와 관련된 드립을 하게 되는군요. 계속해서 말씀드리죠-_-. 어떤 타입이든지 될 수 있는 제네릭을 활용한다면, 메서드를 변경시키지 않고서도 여러타입에 사용가능한 메서드를 만들 수 있습니다. 그럼, 소스를 한번 보시죠.

 class Serializer
{
    internal static bool Serialize<T>(T source, string fileName)
    {
        try
        {
            XmlSerializer serializer = new XmlSerializer(typeof(T));
            Stream stream = new FileStream(fileName, FileMode.Create,
                   FileAccess.Write, FileShare.None);
            serializer.Serialize(stream, source);
            stream.Close();

            return true;
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }

    internal static T Deserialize<T>(string fileName) where T : class, new()
    {
        T target;
        FileInfo _settingFile = new FileInfo(fileName);
        if (!_settingFile.Exists)
        {
            return new T();
        }

        try
        {
            XmlSerializer serializer = new XmlSerializer(typeof(T));
            Stream stream = new FileStream(fileName, FileMode.Open,
                   FileAccess.Read, FileShare.None);
            target = serializer.Deserialize(stream) as T;
            stream.Close();

            return target;
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }
}


위의 소스에서 <T>라고 쓰인부분이 바로 제네릭을 쓰겠다고 하는 겁니다. 즉, 어떤 타입이든지 될 수 있는 T라는 제네릭한 타입에 대해서 메서드를 작성하겠다는 거죠. 그리고 주목하실 부분이, Deserialize메서드 선언부의 끝부분의 "where T : class, new()"입니다. 이건, 제네릭한 타입 파라미터에 제약조건을 주는 겁니다. 즉, 위에 선언된 건 T가 제네릭한 타입이긴한데 class여야만 하고, 파라미터없고 public인 생성자가 있어야 한다는 겁니다. 그런 타입만 T를 통해서 넘어올 수 있다는 거죠. 제약조건은 그 외에도 아래와 같은 것들이 있습니다.

 제약조건  설명
 where T: struct  타입 매개변수는 반드시 Nullable을 제외한 값형(value type)이어야만 한다.
 where T : class  타입 매개변수는 반드시 참조형(reference type)이어야만 한다.
 where T : new()  타입 매개변수는 반드시 public이고 매개변수가 없는 생성자를 갖고 있어야 한다. 그리고 다른 제약 조건이랑 같이 사용될때는 new()가 맨뒤에 와야한다.
 where T : <base class name>  타입 매개변수는 반드시 명시된 클래스를 상속 해야한다.
 where T : <interface name>  타입 매개변수는 반드시 명시된 인터페이스이거나, 명시된 인터페이스를 구현해야 한다.
 where T : U  T에 해당되는 매개변수는 반드시 U에 해당되는 매개변수 타입이거나, U를 상속한 타입이어야 한다. 

네. 뭐 그렇답니다. 프레임워크 개발자나 복잡한 경우가 아니면, 저걸 다 쓸일이 있을지는 모르겠지만 알아두면 좋겠죠. 그리고 위의 코드는 아래와 같이 사용할 수 있습니다. 메서드가 static으로 된건 그냥 편의를 위해서 입니당.

public class Pair
{
    public int Id { get; set; }
    public string Name { get; set; }
}

class Program
{
    static bool SerializePairs(List<Pair> pairs)
    {
        try
        {
            Serializer.Serialize<List<Pair>>(pairs, "Pairs.xml");

            return true;
        }
        catch(Exception ex)
        {
            Console.WriteLine(ex.Message);
            return false;
        }
    }

    static List<Pair> DeserializePairs()
    {
        try
        {
            return Serializer.Deserialize<List<Pair>>("Pairs.xml");
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            return new List<Pair>();
        }
    }

    static void Main(string[] args)
    {
        List<Pair> pairs = new List<Pair>
        {
            new Pair{
                Id=1,
                Name="홍길동"
            },
            new Pair{
                Id=2,
                Name="루저"
            },
            new Pair{
                Id=3,
                Name="위너"
            }
        };

        SerializePairs(pairs);
        List<Pair> pairs2 = DeserializePairs();

        foreach (Pair pair in pairs2)
        {
            Console.WriteLine(
                string.Format("{0} : {1}",
                    pair.Id,
                    pair.Name)
                );
        }
    }
}


Pair라는 객체가 있고, 그 객체에는 홍길동과 루저, 위너 이렇게 3명이 들어갑니다. 그리고 그 객체를 시리얼라이즈하고, 다시 디시리얼라이즈해서 결과를 화면에 출력하고 있죠. 메서드를 호출할때 "Serialize<List<Pair>>(pairs, "Pairs.xml")" 이렇게, <>안에 제네릭한 타입인 T가 실제로 어떤 타입이 될지를 명시해주고 있습니다. 그리고 결과를 보면 아래와 같구요.


넵. 그렇습니다. 별거 없죠-_-;;;


- 여기까지.

넵. 여기까지입니다. 뭐 특별한 내용도 없지만, 그냥 한번 정리하면 저도 편하고 필요하신 분들에게도 편하지 않을까 싶어서 말이죠-_- ㅋ. 소스는 파일로 첨부해드리겠습니다. 필요하신분들은 참고하시구요~. 언제가 될지는 모르겠지만, 다음 포스트에서 뵙져!!!


- 참고자료
1. http://msdn.microsoft.com/en-us/library/d5x73970.aspx

신고

Welcome to dynamic C# 외전(1) - Generate From Usage.

C# 2009.10.25 09:00 Posted by 뎡바1

- 베타2를 맞이하여.

비주얼스튜디오 2010 베타2가 드디어 나왔습니다. 모양도 초큼더 이뻐진거 같구요, 이제 슬슬 제품이 출시될거 같다는 느낌도 드는 군요. 제가 처음 비주얼 스튜디오를 접한게 비주얼스튜디오2002 베타2 버전이었는데요, 왠지 향수가 느껴지네요.-_-;;; 이번 포스트에서는 TDD의 대세를 따라서 코딩을 좀더 편리하게 해주는 기능하나를 소개해드리려 합니다.


- 이름하여 Generate From Usage!

모 이름은 '사용하는 모냥을 봐서 생성하겠다'정도가 되겠네요. 2008까지는 TDD방식으로 개발을 한다고 해도, 클래스등을 미리 만들어 놓고, 메서드 수준에 가서야 메서드 스텁은 생성하면서 TDD방식을 적용할 수 있었습니다. 물론, 편리한 툴의 지원을 받는 면에서 말이죠. 그런데 2010 베타2 부터는 TDD를 편리하게 할 수 있도록 좀 더 멋진기능이 지원됩니다. 한번 살펴보시져!


예들 들어서, Test123이라는 클래스를 TDD로 만들려고 합니다. 그러면, Test123은 당연히 현재 존재하지 않는 클래스이겠죠? TDD가 일반적으로 시나리오를 기반으로 테스트케이스를 먼저 만들고, 그 테스트를 통과하도록 구체적으로 코드를 작성해나가는 방식이니까요. 약간 귀찮은 문제가 여기서 발생합니다. 실제로 TDD를 해보면, 테스트 코드를 작성할때 대상이 되는 클래스를 열심히 타이핑하는데, 기존에 없는 클래스기 때문에 당연히 인텔리센스의 지원이 안되서 날코딩을 해야 하는거죠. 그래서 귀찮으니깐 없는 클래스를 껍데기만 미리 작성해놓고, 인텔리센스의 지원을 받습니다. 그리고 VS2008에서는 스텁생성이 메서드레벨에서만 지원됐기 때문에, 클래스를 선언해놔야 좀더 편리하기 때문이죠.

근데, 만약에 존재하지 않는 클래스라도 인텔리센스의 지원을 받을 수 있다면? 그리고 그 클래스의 스텁을 생성해줌과 동시에, 클래스 파일까지도 생성을 해준다면? 그리고 드라군이 출동한다면?? ........ 아무튼 *라게 많이 편하겠죠?

<그림1>을 보시면, 이런 멋진 기능이 이제 현실로 다가왔음을 느끼실 수 있습니다. 분명히 Test123은 없는 클래스니깐, 빨간 밑줄이 그어졌는데, new뒤에 나오는 인텔리센스에는 Test123이 버젓이 들어가 있습니다!!!



<그림1>버젓이 들어가 있다!!!!


인텔리센스에 버젓이 들어가 있었지만, 아직 정의안된 클래스니깐 <그림2>처럼 에러메세지가 나오는걸 보실 수 있습니다.


<그림2>클래스정의가 엄서요!!


하지만, 예전버전에서 없는메서드에 마우스 오른쪽 버튼을 눌러서 메서드 스텁을 생성했듯이, Test123위에 마우스를 놓고 오른쪽 버튼을 눌러보면, <그림3>처럼 클래스를 생성하는 옵션이 있습니다.



<그림3>참으로 착한 옵션이로닭!!!


그리고 결과를 보면, <그림4>처럼 Test123.cs라는 파일이 생성되었고, <그림5>처럼 껍데기가 작성된 걸 보실 수 있습니다!



<그림4>조...좋은 생성이다1



<그림5>조...좋은 생성이다2

그리고 이번엔, 없는 프로퍼티를 하나 써볼까요? <그림6>처럼 아직 정의가 안된 프로퍼티를 갖다쓰면, 어쩌구 저쩌구 하고 컴파일러가 불평을 합니다.



<그림6>어쩌구 저쩌구...


그러면, 클래스와 같이 오른쪽 버튼을 지그시 눌러주면, <그림7>처럼 필드나 프로퍼티로 생성을 할 수 있습니다. 프로퍼티를 눌러보면 <그림8>과 같이 프로퍼티가 하나 추가된걸 보실 수 있죠.



<그림7>프로퍼티 하나 추가여.



<그림8>조...좋은 생성이다3


- 마치면서

VS2008에서 TDD를 아주 허접하게 해본 경험으로 미뤄볼때, 확실히 좀더 잔손질을 줄여주는 기능이 될 거 같습니다. 뭐 꼭 TDD가 아니더라도 기능을 활용할 일은 많은 거라는 생각이 드네요. 전 이래서 비주얼 스튜디오가 좋습니다. ㅋㅋㅋ....-_-;;;
신고

Welcome to Dynamic C#(6) - Return to Dynamic (2)

C# 2009.09.03 13:36 Posted by 뎡바1

- 복습.

지난시간에서 이야기 했던 부분을 조금 이어서 이야기하자면요, dynamic이라는 타입이 생겼고 이 타입은 런타임에 가서야 실제로 담고있는 타입이 뭔지 수행하고자 했던 연산이 존재하는지 등을 알 수 있습니다. 그리고 닷넷 프레임워크 내부적으로는 dynamic이라는 타입은 없으며, object타입에 dynamic 어트리뷰트가 붙어서 런타임에게 적절한 동적 연산을 수행하도록 알려주도록 하고 있었습니다. 그리고 dynamic과 관계된 연산을 만나면, 컴파일러는 DLR의 call site를 이용하는 코드를 생성하구요 DLR에 정의된 기본연산들중에 C#에 알맞도록 상속된 클래스를 생성하고 그 연산을 call site를 통해서 호출하도록 코드를 생성해줍니다.

그러면 잠깐 실제로 동적연산을 호출하면 런타임에 어떤 절차를 거치게 되는지 알아보도록 하겠습니다.


- 복습은 거기까지.

dynamic d = ......;
d.Foo(1, 2, d); 

위와 같은 코드가 실행된다고 할때 대략 아래와 같은 절차를 따르게 됩니다.

1. DLR이 사용자가 넘겨주는 매개변수의 타입(int, int, dynamic)으로 요청받은 액션(InvokeMember)이 캐시되어 있는지 확인합니다. 캐시에 저장이 되어 있다면, 캐시되어있던 걸 리턴합니다.

2. 캐시에 해당되는 내용이 없다면, DLR은 액션을 요청받는 객체가 IDynamicObject(이하 IDO)인지 확인합니다. 이 객체는 스스로 어떻게 동적으로 바인딩하는지를 알고 있는 객체입니다.(COM IDispatch object, 루비나 파이썬의 객체나 IDynamicObject 인터페이스를 구현한 닷넷 객체들 처럼). 만약 IDO라면 DLR은 IDO에게 해당 액션을 바인딩해달라고 요청합니다. IDO에게 바인딩을 요청한 뒤에 받는 결과는 바인딩의 결과를 나타내주는 expression tree입니다.

3. IDO가 아니라면, DLR은 language binder(C#의 경우는 C# runtime binder)에게 해당 액션을 바인딩해줄 것을 요청합니다. 그러면 C# runtime binder가 그 액션을 바인딩하고 바인딩의 결과를 expression tree로 리턴해줍니다.

4. 2번이나 3번이 수행된 다음엔 결과로 받은 expression tree가 DLR의 캐시속으로 통합되고 같은 형태의 요청이 다시 들어온다면 바인딩을 위한 절차를 수행하지 않고 캐시에 저장된 결과를 가지고 실행하게 됩니다.


그리고 이어서 C# runtime binder에 대해서 소개해 드릴텐데요, C# runtime binder는 무엇을 어디에 바인딩할지를 결정하는 심볼테이블을 reflection을 이용해서 생성합니다. 만약에 컴파일타임에 타입이 정해진 매개변수라면 C# 액션의 정보에 해당 타입으로 기록되고 런타임시에 바인딩될때 그 매개변수는 기록된 타입으로 사용될 수 있겠죠. 근데 만약에 컴파일 타임에 dynamic으로 결정된 매개변수라면(dynamic타입의 변수나, dynamic을 리턴하는 표현식), runtime binder는 reflection을 이용해서 그 매개변수의 타입을 알아내고, 알아낸 타입을 그 매개변수의 타입으로 사용하게 됩니다.

- 심볼테이블?

심볼테이블은 컴파일러나 인터프리터가 코드를 번역하기 위해서 사용하는 자료구조인데요. 코드의 변수명, 메서드등의 식별자를 각각 타입이나 범위, 그리고 메모리에서의 위치등을 기록합니다. 아래의 표는 위키피디아에 있는 심볼테이블의 예제인데요, 메모리의 주소와 심볼의 타입과 심볼의 이름으로 구성되어 있습니다. 

Address Type Name
00000020 a T_BIT
00000040 a F_BIT
00000080 a I_BIT
20000004 t irqvec
20000008 t fiqvec
2000000c t InitReset
20000018 T _main
20000024 t End
20000030 T AT91F_US3_CfgPIO_useB
2000005c t AT91F_PIO_CfgPeriph
200000b0 T main
20000120 T AT91F_DBGU_Printk
20000190 t AT91F_US_TxReady



runtime binder는 심볼테이블을 필요할때 필요한 만큼 만드는데요, 위의 짧은 예제에서 처럼 Foo라는 메서드를 호출하는 경우라면 runtime binder는 d의 런타임 타입에 대해서 Foo라는 이름을 가진 멤버들을 모두 로드합니다. 그리고 형변환역시 요구되는 형변환들을 모두 심볼테이블로 로드합니다. 그리고 런타임 바인더는 C# 컴파일러가 하는 것과 동일한 오버로딩 판별알고리즘을 수행합니다. 그리고 컴파일시에 받는 것과 동일한 문법을 사용하며, 동일한 에러, 동일한 예외를 출력합니다. 그리고 마지막으로 이런과정을 거친 결과를 expression tree로 생성하고 DLR에게 리턴해줍니다. 단, 여기서 사용하는 expression tree는 C# 3.0의 expression tree를 확장한 것입니다. 기존의 expression tree에 동적인 연산, 변수, 이름 바인딩, 흐름제어를 위한 노드들이 추가된 거죠.


- 마치면서

뭔가, 짧고 설명도 어색한 포스트인거 같네요;;; 이번시간을 통해서 DLR이 코드를 실행하기 위해서는 코드를 내부적으로 expression tree라는 형태로 가지고 있음을 알아봤습니다. 다음시간엔 C# 3.0에서 처음등장한 expression tree와 DLR에서 사용하는 expression tree에 대해서 설명을 드리도록 하겠습니다.


- 참고자료

1. http://blogs.msdn.com/samng/archive/2008/10/29/dynamic-in-c.aspx
2. http://en.wikipedia.org/wiki/Symbol_table

신고

Welcome to Dynamic C#(5) - Return to Dynamic.

C# 2009.08.26 15:02 Posted by 뎡바1

- 그때 너 너무 대충하더라

제가 맨 처음 dynamic에 대한 포스트로 Welcome to Dynamic시리즈를 시작했는데요. 뭐랄까 너무 추상적인 내용위주로 진행했다는 생각이 들더군요. 그리고 공부를 더 하다보니 그런 생각이 더 확실해지더군요. 없는 input에서 output이 나올수는 없는거니 당연한 이야기 겠지요. 캬캬캬. 앞으로는 dynamic과 DLR에 대해서 이야기를 조금 진행해보려고 합니다. 그래봤자 여전히 별 내용없거나 다른분들이 주는 insight를 그대로 전해주는 역할 이상은 못할지도 모르지만 일단 늘 그래왔듯이 노력해보겠습니다. 따쓰한 피드백을. ㅋㅋㅋㅋ


- dynamic?

dynamic d = ....;
d.Foo();

여기서 지역변수인 d는 dynamic타입을 가집니다. dynamic은 엄연히 컴파일러가 지원하는 타입이고, 타입이름이 들어갈 수 있는 곳에는 어디든지 dynamic이라고 명시해줄 수 있습니다. 즉, 실제타입이 동적으로 결정되는 것을 의미하는 정적인 타입인거죠. 차이점이라고 한다면, Foo라는 메서드를 호출하는 IL코드를 바로 만든다기 보다는 DLR과 C# 런타임 바인더를 통해서 dynamic call site라는 걸 호출되는 지점에서 생성합니다. 

이런 기능을 통해서 여러분이 기존에 써오던 친숙한 방법과 모습으로 파이썬이나 루비, 혹은 "스스로 어떻게 실행해야 하는지 알고있는" 객체들을 사용할 수 있게 해줍니다. 개인적으로는 이 부분이 꽤나 중요한 부분이라고 생각합니다. 다른 동적언어들이 있는데, 굳이 C#에 이런 기능이 들어가는건 앤더스 헬스버그의 철학답게, 기존에 잘 사용해오던 언어에 새로운 기능을 잘 통합시켜서 한 부분에만 특화된 언어보다 기존의 언어에서도 새로운 기능을 사용할 수 있게 해주는 거겠죠. 그래서 기존에 잘 사용해오던 언어가 새로운 요구에 발맞추는 새로운 표현법을 계속해서 잘 통합시켜 나가면서 생명력을 유지할 수 있게 말이죠.

위에서 말씀드렸듯이 dynamic은 분명히 존재하는 타입이고 컴파일러도 잘 알아듣는 타입이지만, 현재까지의 모습으로 봤을땐 실제로는 존재하지 않는 타입입니다. DLR과의 연동을 통해서 가능한 동적인 프로그래밍을 문법적으로 편리하게 만들어주는 syntatic sugar같은 역할이라고 볼 수 있을까요? 일단 아래코드를 보시져.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace ConsoleApplication2
{
    class Program
    {
        public dynamic DynamicCall(dynamic d)
        {
            object obj = 5;
            return d.Foo();
        }

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


그리고 이 코드에서 타입위에 마우스를 가져가 보시져. object와 dynamic을 비교해보겠습니다.




object위에 마우스를 올렸을때는 "class System.Object"라고 나오는데, dynamic에는 그런 표시가 없죠? 그럼 우리의 심증을 물증으로 굳혀보겠습니다. 위의 코드를 컴파일한 코드를 리플렉터에서 보면 아래와 같습니다.

----- 리스트 1 -----

[return: Dynamic]
public object DynamicCall([Dynamic] object d)
{
    if (<DynamicCall>o__SiteContainer0.<>p__Site1 == null)
    {
        <DynamicCall>o__SiteContainer0.<>p__Site1 =
            CallSite<Func<CallSite, object, object>>.Create(
                new CSharpInvokeMemberBinder(
                    CSharpCallFlags.None, "Foo", typeof(Program), null,
                    new CSharpArgumentInfo[] {
                        new CSharpArgumentInfo(CSharpArgumentInfoFlags.None, null) }));
    }

    return <DynamicCall>o__SiteContainer0.
                    <>p__Site1.Target(<DynamicCall>o__SiteContainer0.<>p__Site1, d);
}

dynamic이라는 타입은 싹 사라지고 object만 덩그러니 있는걸 확인할 수 있습니다. 그리고 dynamic이라고 알려주는 지시자같은게 붙어있는걸 보실 수 있습니다. 즉 beta1기준으로 현재에는 dynamic은 엄연히 하나의 타입이지만, 닷넷 프레임워크 내부적으로는 dynamic이라는 타입이 존재하지 않는다는 말입니다. 즉, 저렇게 dynamic이라고 표시가 된 객체는 컴파일러가 런타임에게 동적으로 처리되어야 한다는 걸 알려주는게 되겠죠.

그리고 위에서 말씀드렸듯이 컴파일러는 dynamic과 관계된 연산을 만나게 되면, DLR을 통해서 DLR의 call site를 이용하는 코드를 생성합니다. 코드에 보시면 'SiteContainer', 'CallSite'같은게 보이시죠? DLR에 기반한 동적언어에서 어떤 동적 연산을 호출하면 그 코드는 DLR이 이해할 수 있는 기본적인 연산으로 번역되고, 그 기본적인 연산을 적용할 대상에 따라서 Python객체에 하려면 Python Binder로, 기본적인 .NET 객체에는 Object Binder로, C#은 C#런타임 바인더로 적용하게 됩니다. 그 기본적인 연산의 목록은 현재 아래와 같습니다.

----- 리스트 2 -----

namespace System.Dynamic
{
    public class DynamicObject : IDynamicMetaObjectProvider
    {
        protected DynamicObject();

        public virtual IEnumerable<string> GetDynamicMemberNames();

        public virtual DynamicMetaObject GetMetaObject(Expression parameter);

        public virtual bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object result);

        public virtual bool TryConvert(ConvertBinder binder, out object result);

        public virtual bool TryCreateInstance(CreateInstanceBinder binder, object[] args, out object result);

        public virtual bool TryDeleteIndex(DeleteIndexBinder binder, object[] indexes);

        public virtual bool TryDeleteMember(DeleteMemberBinder binder);

        public virtual bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result);

        public virtual bool TryGetMember(GetMemberBinder binder, out object result);

        public virtual bool TryInvoke(InvokeBinder binder, object[] args, out object result);

        public virtual bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result);

        public virtual bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value);

        public virtual bool TrySetMember(SetMemberBinder binder, object value);

        public virtual bool TryUnaryOperation(UnaryOperationBinder binder, out object result);
    }
}


위의 목록에서 보시면 TryInvokeMember메서드가 있고 인자로는 InvokeMemberBinder를 받는게 보이시져? 그리고 위의 리플렉터에서 뽑은 리스트1을 보시면, Create메서드의 인자로 CSharpInvokeMemberBinder를 생성하고 있습니다. 그리고 CSharpInvokeMemberBinder를 따라가보면, base클래스가 InvokeMemberBinder가 나옵니다. 즉, DLR이 이해할 수 있는 기본연산이 C# 바인더를 통해서 실행되고 있을음 유추해볼 수 있습니다.

그리고 리스트1에서 "<>p_Site1" 이라는 걸 따라가보면 선언이 아래와 같습니다.

public static CallSite<Func<CallSite, object, object>> <>p__Site1;

즉, static 필드인데요. 델리게이트도 담고 있습니다. 이게 Foo메서드 호출에 대한 dynamic call site를 가지고 있는 필드입니다. 위의 리스트1의 코드를 보시면, 이 <>p_Site1에 저장된 내용을 Target메서드를 통해서 호출하고 있는 모습을 보실 수 있습니다. 생긴게 좀 복잡하긴 한데요, 이걸 뭐 직접 짜야하는건 아니고 컴파일러가 작업해주는 거니까요. 이 이야기는 나중에 더 자세하게 다루도록 하구요. 일단 dynamic이 어디에 어떤 모습으로 쓰일 수 있고, 그게 뭘 의미하는지 더 알아보도록 하겠습니다. 우선, 아래의 코드를 보시져. 

dynamic d = ...;

d.Foo(1, 2, 3); // (1)

d.Prop = 10; // (2)

var x = d + 10; // (3)

int y = d; // (4)

string y = (string)d; // (5)

Console.WriteLine(d); // (6)
(d.Foo(); 에서 d는 Foo의 실행요청을 받는 receiver이고, d의 타입이 dynamic이라면 d는 dynamic receiver가 되는거임!)

1. Foo메서드의 호출요청을 받은 객체의 타입이 dynamic이므로 컴파일러는 런타임에게 이 코드에서 d의 실제 runtime type이 뭐든지에 상관없이 "Foo"라는 메서드를 매개변수{1, 2, 3}를 적용해서 바인드해야 한다는걸 알려줍니다.

2. 역시 1번과 마찬가지로 dynamic receiver가 있으므로 컴파일러는 런타임에게 이 코드에서는 "Prop"이라는 프로퍼티비스무리한(필드나 프로퍼티)걸 바인드해야 하고 거기에 10이라는 값을 할당해야 한다고 알려줍니다.

3. 여기서는 +연산자는 동적으로 바인드되는 연산인데요, 매개변수중에 하나가 dynamic이기 때문이죠. 런타임은 실제 d의 runtime type에 대해서 일반적인 연산자 오버로딩 규칙을 따라서 적합한 연산을 찾습니다.

4. 여기서는 암시적인 형변환이 있는데요, 컴파일러는 int와 d의 runtime type에 대한 모든 형변환을 고려해본뒤에 d에서 int로의 형변환이 가능한지 판단하도록 런타임에게 알려줍니다.

5. 이번에는 명시적인 형변환 인데요, 컴파일러는 이 변환을 컴파일하고 런타임에게 이 형변환에대해서 검토해보도록 알려줍니다.

6. 비록 컴파일타임에서 볼 수 있는 메서드를 호출하지만, 인자가 dynamic이므로 컴파일타임에서는 오버로딩 판별을 할 수 없습니다. 그래서 어떤 Console.WriteLine을 호출할지도 역시 런타임에 결정하게 됩니다.


- 마치면서

오늘은 dynamic타입에 대해서 이야기 해봤습니다. 재주가 부족해서 잘 설명한거 같지 않네요;;; 생각보다 dynamic배후의 이야기가 많은데요, 다음시간부터 거기에 대해서 하나씩 하나씩 이야기 해보겠습니다~.


- 참고자료

1. http://blogs.msdn.com/samng/archive/2008/10/29/dynamic-in-c.aspx
2. http://blogs.msdn.com/cburrows/archive/2008/10/27/c-dynamic.aspx
3. http://channel9.msdn.com/pdc2008/TL10/

신고

Welcome to Dynamic C#(4) - 극과극 비교체험.

C# 2009.08.20 20:25 Posted by 뎡바1

- 또 쓸데없는 생각 하냐?

안녕하세요. 정말 오랜만입니다. 사연이 많은 사람이다 보니, 잠수를 자주 타게 되네열. -_-;;;;; 그래서 뭐라도 써야한다는 생각을 하다가, 별로 쓸모있을진 모르겠지만, 실행속도를 비교해보자는 생각이 들었습니다. 짧은 글이 되겠지만, 조금이라도 도움이 되길바라면숴!


- 빨랑 비교한거 내놔.

비교대상은 한 클래스에 있는 메서드를 그냥 호출하는 것과 dynamic을 통해 호출하는 것, 그리고 리플렉션을 통해서 호출하는 세가지방법입니다. 그리고 각 호출을 메서드를 10만, 50만, 100만, 300만, 500만번 호출하는 것으로 속도를 재어봤습니다. 실행의 대상이 된 코드는 아래와 같습니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace ConsoleApplication2
{
    class Test
    {
        public int FivePlusFive()
        {
            return 10;
        }
    }

    class Program
    {
        public void ReflectionCall()
        {
            object test = new Test();
            Type type = test.GetType();
            type.InvokeMember("FivePlusFive", System.Reflection.BindingFlags.InvokeMethod, (Binder)null,
                test, new object[] {});
        }

        public void DynamicCall()
        {
            dynamic test = new Test();
            test.FivePlusFive();
        }

        public void NormalCall()
        {
            Test test = new Test();
            test.FivePlusFive();
        }

        static void Main(string[] args)
        {
            Program prog = new Program();

            //for JIT compile
            prog.ReflectionCall();
            prog.DynamicCall();
            prog.NormalCall();

            long limit = 5000000;

            DateTime normalStart = DateTime.Now;
            for (int i = 0; i < limit; i++)
            {
                prog.NormalCall();
            }
            DateTime normalEnd = DateTime.Now;
            TimeSpan normalResult = normalEnd - normalStart;

            DateTime dynamicStart = DateTime.Now;
            for (int i = 0; i < limit; i++)
            {
                prog.DynamicCall();
            }
            DateTime dynamicEnd = DateTime.Now;
            TimeSpan dynamicResult = dynamicEnd - dynamicStart;

            DateTime reflectionStart = DateTime.Now;
            for (int i = 0; i < limit; i++)
            {
                prog.ReflectionCall();
            }
            DateTime reflectionEnd = DateTime.Now;
            TimeSpan reflectionResult = reflectionEnd - reflectionStart;

            Console.WriteLine("Normal Time : {0}", normalResult);
            Console.WriteLine("Dynamic Time : {0}", dynamicResult);
            Console.WriteLine("Reflection Time : {0}", reflectionResult);
        }
    }
}



JIT컴파일에 걸리는 시간을 빼기 위해서 일단 한번씩 먼저 실행했구요, 각각의 방법을 정해진 횟수만큼 실행해서 시간을 측정하는 방식으로 했습니다. 그럼 결과를 보시져!!!! 야호!!!! 완전 신나!!!! -_-......

- 10만번

- 50만번

- 100만번

- 300만번

- 500만번


그리고 위의 결과를 표로 종합해보면 아래와 같습니다.


일반 호출은 리플렉션에 비해서 너무 작아서 그런지 아예 나타나지도 않는군요-_-;;;; 별로의미있는 코드로 실험을 한건 아니지만, 다이나믹이 리플렉션에 비해서는 월등히 빠르군요. 아마도 DLR의 힘을 빌려서 리플렉션 보다 훨씬 빠른 방식을 이용하는 것 같습니다. 이 부분에 대해서는 좀 조사를 해봐야 할거 같네요.


- 피드백 및 정정사항!!ㅋ

이 글을 보시고 정성태님께서 피드백을 주셨습니다.(http://www.sysnet.pe.kr/Default.aspx?mode=2&sub=0&detail=1&pageno=0&wid=766&rssMode=1&wtype=0) 정성태님의 블로그를 들르면서 내공의 깊이에 감탄을 하곤했는데, 직접 피드백을 받으니 더 확실하네 느껴지네요^^ㅋ. 글의 내용을 보시면, 제가 단순히 리플렉션 호출을 반복하게 설정해놓은 것에서 리플렉션에 매우 불리한 결과가 도출되는 요인이 있음을 지적하시고, 더 빠르게 그리고 오히려 다이나믹 보다도 더 빠른 결과가 나올 수 있는 방식을 제시해주고 계십니다.

잘 몰랐던 부분에 대해서 지적해주셔서 좋은거 배웠네요~.


- 마치면서

별로 내용도 없는 글을 썼군요-_-;;; 다음 포스트부터는 dynamic에 대해서 좀 더 심도 깊게 파보려고 생각중입니다. 좋은글이 많은데 잠수타고 정신줄 놓느라고 못보고 있었더군요!! 암튼. 곧 돌아오겠슴돠. ㅋㅋㅋ

신고

Welcome to Dynamic C#(3) - 마음이 넒어진 C#

C# 2009.06.26 08:58 Posted by 뎡바1
- 대인배 모드

C#은 점점 더 마음이 넓어지고 있습니다. F#이라는 조금은 독특하지만 머리좋은 친구가 좋은 기능을 소개해주기도 하구요(C# 2.0의 Generics는 F#의 배후에 있는 Don Syme이 제안한거라는 군요), 팬들이 많아지면서 점점 더 넓은 마음을 갖추게 되었습니다. 그리고 대인배 모드에 또 중요한 역할을 해주는게 있는데요 Co- Contravariance입니다.

우선 그다지 친근하지 않은 이름이네요. 이건 대체 뭘까요? 일단 정리해서 말씀드리면, C# 3.0에서 불필요하게 캐스팅을 하면서 해야 했던 작업을 매우 편하게 할 수 있도록 도와주는 C# 4.0의 새로운 기능입니다. C# 3.0에서 못하게 막았던 부분중에서 문제를 일으킬 가능성이 없는 부분에 대해서는 편리하게 사용할 수 있도록 지원해준다는 말인데요. 뭔가 딱 듣기만 해도 대인배스러운 느낌이 들지 않나요 ㅋ. 무슨 이야기 인지 예제를 보면서 확인해보겠습니다. Eric Lippert의 기가막힌 글이 있는데요, 그 글을 참고로 하면서 작성한 글입니다.(링크는 아래 참고자료쪽에!) 


- 예제를 내놓지 않으면 구워먹겠다.

일단, 동물들에게 먹이를 주는 메서드를 통해 알아보도록 하지요. 아래와 같은 코드를 작성합니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Co_ContraVariance
{
    class Animal
    {
        public bool Hungry { get; set; }

        public override string ToString()
        {
            return "Animal";
        }
    }

    class Mammal : Animal
    {
        public override string ToString()
        {
            return "Mammal";
        }
    }

    class Giraffe : Mammal
    {
        public override string ToString()
        {
            return "Giraffe";
        }
    }

    class Tiger : Mammal
    {
        public override string ToString()
        {
            return "Tiger";
        }
    }

    class Program
    {
        void FeedAnimals(IEnumerable<Animal> animals)
        {
            foreach (Animal animal in animals)
            {
                if (animal.Hungry)
                {
                    Feed(animal);
                }
            }
        }

        private void Feed(Animal animal)
        {
            Console.WriteLine(animal.ToString());
        }

        static void Main(string[] args)
        {
            List<Giraffe> giraffes = new List<Giraffe>{
                new Giraffe{ Hungry = true},
                new Giraffe{Hungry = true},
                new Giraffe{Hungry = false}
            };

            Program program = new Program();
            program.FeedAnimals(giraffes);
        }
    }
}



딱히 잘못된 부분이 보이지 않는 코드입니다. 하지만, vs2008에서는 아래와 같은 에러가 납니다.

 


즉, 다른 타입이므로 메서드를 오버로드하라는 이야기지요. Giraffe는 Animal의 서브타입이므로 문제가 없습니다만, IEnumerable<Giraffe>는 IEnumerable<Animal>과 담고있는 요소의 타입이 다를뿐 어떤 관계도 없습니다. 그래서 IEnumerable<Giraffe>에서 IEnumerable<Animal>로는 형변환이 안되는 것이죠. 그래서 아래와 같은 코드를 통해 간접적으로 형변환을 해줘야 합니다.

program.FeedAnimals(giraffes.Cast<Animal>()); 


 
그런데, 안전한 경우에 한해서 이런게 잘 되게 해주겠다는 게 C#4.0의 이야기입니다. 일단 실행결과를 보시죠.

 

 

C#4.0으로 짠 코드는 잘 실행되는게 보이시죠? 그렇습니다. Covariance를 통해서 코드가 잘 실행된 것입니다. 그러면 이런 Covariance와 Contravariance에 대해서 좀 더 알아보죠.

일단, 간략하게 정의를 한번 내려보도록 하겠습니다.

우선 어떤 타입 T와 U가 있다고 했을때, 다음중 하나는 참이 됩니다.

1. T는 U보다 크다
2. T는 U보다 작다
3. T와 U는 같다
4. T와 U는 서로 관련이 없다.

위의 코드에서 보듯이, Giraffe는 Mammal의 서브타입이므로 Mammal보다 작습니다. 하지만, Giraffe는 Tiger와는 아무관련이 없는 타입이죠. 이런 타입간의 관계에서 어떤 연산을 T와 U에 대해서 수행했을때, 그 결과로 나온 T'와 U'가 T와 U의 관계를 그대로 유지한다면, 그연산은 covariant하다고 이야기 할 수 있으며, 대입의 방향성과 크고작음을 뒤집고 같음과 관계없음만 그대로 유지한다면(위 리스트에서 1,2번을 뒤집고 3,4번만 유지한다면) 이 연산은 contravariant하다고 이야기 할 수 있습니다.


잘 이해가 되시나요? 무슨 꼭 수학의 증명같은 냄새가 나서 거부반응이 일어나신 분들도 있을거 같군요. 그럼, 예제를 통해 차근차근 설명드리도록 하겠습니다.

 

 public interface IEnumerable<T> : IEnumerable
    {
        // 요약:
        //     Returns an enumerator that iterates through the collection.
        //
        // 반환 값:
        //     A System.Collections.Generic.IEnumerator<T> that can be used to iterate through
        //     the collection.
        IEnumerator<T> GetEnumerator();
    }

위 코드는 C#3.0의 IEnumerable의 정의인데요. 이 인터페이스에서 형식매개변수인 T는 GetEnumerator를 통해서 리턴될때만(output position에서만) 사용되는 걸 볼 수 있습니다. 즉, T의 요소를 편집할 방법이 없다는 이야기지요. 이런 경우는 형식매개변수의 대입의 방향성을 유지한 상태에서 IEnumerable<Giraffe>에서 IEnumerable<Animal>같은 형변환을 가능하게 해주겠다는 이야기 입니다.

 

- 대입의 방향성

Giraffe는 Animal의 서브타입입니다. 그렇다면 아래와 같은 코드가 가능하죠.

Animal animal = new Giraffe;

즉, Animal이 Giraffe보다 더 큰(상위) 타입이기 때문에 가능하죠. 하지만 아래의 코드는 컴파일되지 않습니다.

Giraffe giraffe = new Animal();

아래와 같이 하면 컴파일은 문제가 없습니다.

Giraffe giraffe = (Giraffe)new Animal();

하지만, 런타임에서 형변환이 불가능하다고 하면서 예외가 발생되죠. 즉, Animal이 더 크기 때문에, Giraffe는 Animal에 대입될 수 있지만, Animal은 Giraffe에 대입될 수 없습니다. 이게 크고작음에 관련된 대입의 방향성입니다.

단, Giraffe와 Tiger는 둘다 Mammal의 서브타입이지만, 둘사이에는 아무관련이 없습니다.



즉, 이야기를 이어 가자면 Giraffe가 Animal에 대입가능하기 때문에, 형식매개변수가 리턴될때만(output position에서) 사용된다면, 대입의 방향성을 유지한상태에서는 IEnumerable<Giraffe>에서 IEnumerable<Animal>로 변환을 하는것 같이 참조형변환을 지원해주겠다는 이야기 입니다.

 
반대로, Contravariance는 파라미터로 받기만하는 경우에만 사용할 수 있습니다. 그리고 대입의 방향성 역시 반대로 뒤집어 버립니다.

 public interface IComparer<T>
    {
        // 요약:
        //     Compares two objects and returns a value indicating whether one is less than,
        //     equal to, or greater than the other.
        //
        // 매개 변수:
        //   x:
        //     The first object to compare.
        //
        //   y:
        //     The second object to compare.
        //
        // 반환 값:
        //     Value Condition Less than zero x is less than y.  Zero x equals y.  Greater
        //     than zero x is greater than y.
        int Compare(T x, T y);
    }


위 코드는 특정타입에 대해서 비교를 지원해주고 싶을때 구현하는 인터페이스인데요, 여기도 역시 보시면, 형식매개변수인 T는 Compare메서드에서 비교할 인자를 받는 곳(input position)에서만 사용되는 걸 볼 수 있습니다. 예제를 위한 예제가 될 수 도 있지만, IComparer<T>를 통해서 contravariance를 설명해보죠~. 

위에서 작성한 코드에 아래와 같은 코드를 추가합니다. 

    class AnimalComparer : IComparer<Animal>
    {
        public int Compare(Animal x, Animal y)
        {
            return 1; //예제를 위해 ㅋ.
        }
    }

    class GiraffeComparer : IComparer<Giraffe>
    {
        public int Compare(Giraffe x, Giraffe y)
        {
            return 1; //예제를 위해 ㅋ.
        }
    }

.......중략.......

        static void Main(string[] args)
        {
            ........중략.......

            IComparer<Animal> animalComp = new AnimalComparer();
            IComparer<Giraffe> giraffeComp = animalComp;
            Console.WriteLine(giraffeComp.Compare(new Giraffe(), new Giraffe()));
        }



각각 IComparer<Animal>과 IComparer<Giraffe>를 구현한 비교 클래스입니다. 그리고 IComparer<Animal>을 IComparer<Giraffe>에 대입하고 있습니다. 그리고 아무문제없이 컴파일 됩니다. 그리고 실행도 문제없이 됩니다.



Animal은 Giraffe보다 더 큰 데 어떻게 IComparer<Giraffe>에 들어가는 걸까요? 그냥 상식적으로 생각했을때 동물을 비교할 수 있다면, 기린(giraffe)을 비교할 수도 있겠죠? 사람들이 IComparer<Giraffe>에 비교하려고 넣는 객체는 모두 Giraffe타입이겠고 giraffeComp가 실제로 호출하게 되는 Compare메서드는 IComparer<Animal>을 구현한 animalComp의 메서드겠죠. 그래서 방향성이 뒤집혔지만, contravariance로 인해서 실행가능한 코드가 됩니다.

반대로 IComparer<Animal>에다가 IComparer<Giraffe>를 대입하는 경우를 생각해보죠. 그렇다면 사람들은 animalComp를 통해서 Animal을 비교하고 싶어할텐데, 실제로 호출될 메서드는 IComparer<Giraffe>의 Compare메서드겠죠. 그 메서드는 Giraffe타입을 받을 수 있으므로 Animal을 받을 수 없습니다. 그래서 이런 참조형변환은 지원되지 않습니다.

즉, contravariance는 형식인자가 파라미터로 입력되는 부분에서만(input position에서만) 쓰일때, 대입의 방향성을 뒤집는 참조형변환을 지원해주겠다는 이야기 입니다. 일단은 여기까지가 co- contravariance에 대한 간략한 설명입니다. 


- 대인배 만쉐이 ㅋ

암튼, 점점 더 대인배가 되어가는 C#의 모습을 살짝 엿보았습니다. 어떻게 보면, 그냥 직관적으로 할 수 있는 부분들에 대해서 제약을 풀어준 셈이니까 대인배전략에 걸맞는 기능인 거 같습니다. 물론 마냥 낙관적인 것만은 아닙니다만. 성격좋은 사람도 나름의 규칙은 있는 법이니까요. 안전하고 적절할 co- contravariance의 사용은 "들어가는 거(형식매개변수)는 contravariant일 수 있고, 나가는 거(return되는)는 covariant일 수 있다."라는 군요. 그렇지 않은 경우도 있다는 말이겠죠. 제 내공이 허락한다면 이부분에 대해서도 정리해서 올리도록 하겠습니다. 대인배 C# 만쉐이 캬캬캬.

 

- 참고자료

1. http://blogs.msdn.com/ericlippert/archive/2007/10/16/covariance-and-contravariance-in-c-part-one.aspx
2. http://blogs.msdn.com/ericlippert/archive/2007/10/17/covariance-and-contravariance-in-c-part-two-array-covariance.aspx
3. http://blogs.msdn.com/ericlippert/archive/2007/10/19/covariance-and-contravariance-in-c-part-three-member-group-conversion-variance.aspx
4. http://blogs.msdn.com/ericlippert/archive/2007/10/22/covariance-and-contravariance-in-c-part-four-real-delegate-variance.aspx
5. http://blogs.msdn.com/ericlippert/archive/2007/10/26/covariance-and-contravariance-in-c-part-five-interface-variance.aspx
6. New features in C# 4.0, Mads Torgersen

신고

Welcome to Dynamic C#(2) - Wanna be a polyglot.

C# 2009.05.17 16:25 Posted by 뎡바1

- 자넨 왜 그렇게 언어에 집착하는고?

넵, 확실히 저는 언어에 쫌 집착하는 편이긴 합니다. 우리가 말하고 쓰는언어도 꽤나 집착하는 편입니다. 언어를 제대로 배우려고 노력하다보면, 더 재밌는 걸 많이 접할 수 있게 되고 그 언어뿐만 아니라 언어를 쓰는 사람들의 사고방식도 아주 조금씩 이해하게 되기 때문이죠. 뭔가 보고싶은게 있는데 그게 제가 모르는 언어로 되어 있어서 못보는건 조금 슬픈일인거 같습니다. 개인적으로 부족한 실력이지만, 미드&일드를 아주 재밌게 즐기고 있습니다.

프로그래밍 언어도 비슷한 의미에서 집착하게 되는게 아닐까 싶습니다. 프로그래밍 언어에는 그 언어를 만들고 지지하는 사람들의 사고방식도 같이 배울 수 있게 되고, 점점 재밌게 할수 있는게 늘어나기 때문이죠. 최근에 언어에 대해서 아주 부족한 의견이지만 글을 썼던 적이 있습니다. 관심있으신 분은 보시고 따쓰한 피드백 주시면 완전 감사하겠습니다. 

아무튼 그런의미에서 F#에 C#에 Axum까지 건드려보고 있는거지요. 줏대가 없다거나 바람기가 있다거나 뭐 그런건 아닙니다. 그럼 본론으로 들어가서, 지난번엔 동적C#에 대한 이야기를 조금 드렸었습니다. 하지만 정작 알고 싶은건 dynamic키워드란게 생겼고 대충 오리꽥꽥 어쩌구 저쩌구 하는건 알겠는데, 어디다 써먹는거란 말이더냐? 뭐 그런거겠죠. 그래서 쌩초보이지만, 최대한 그런관점에서 접근해보고자 합니다. 그 첫번째가 실제로 프로젝트를 하다가 하나 느낀게 있어서 그걸 적어보고자 합니다. 



- 시나리오
(제가 모르는 해결방법이 있을수도 있습니다. 그럴땐 따쓰한 피드백을!)

LINQ to SQL(이하 L2S)로 프로젝트를 진행중입니다만, 데이터가 추가되거나, 수정되거나 삭제될때 그 값들의 이전/이후 데이터를 포함해서 그 데이터의 고유번호(seq)를 같이 저장하는 뭐 그런 시나리오입니다. 그래서 L2S에서는 아래와 같은 방법을 제공합니다.

 

ChangeSet changeSet = db.GetChangeSet();

foreach (Customer c in changeSet.Inserts)
{
	.......
}

 

하지만, 위와 같은 코드는 한번의 SubmitChanges로 변경이 일어나는 대상 테이블이 하나라면 Generic파라미터로 처리할 수도 있겠지만(ChangeSet에서 리턴되는 객체의 타입이 object입니다), 주문과 상세주문같이 한번에 여러개의 테이블에 변경이 일어난다면, 그닥 친절하지 못한 시나리오가 되겠습니다. 그래서 그때 들었던 생각이 "아 이거 dynamic키워드를 이용해서 오리꽥꽥타이핑(duck typing)을 이용하면, 쫌 쉽게 될거 같은뎅..." 였습니다. 그래서 한번 살짝 구현해봤습니다.

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace FirstDynamic
{
    class Customer
    {
        public int Seq{get; set;}
        public string Name {get; set;}        
    }

    class Company
    {
        public int Seq{get; set;}
        public string Name{get; set;}
    }

    class Project
    {
        public int Seq{get; set;}
        public string Name{get; set;}
    }

    class NoneSeqThing
    {
        public string Name{get; set;}
    }

    class Logger
    {
        public void WriteLog(dynamic entity)
        {
            try{
                Console.WriteLine(entity.Seq);
            }
            catch(Microsoft.CSharp.RuntimeBinder.RuntimeBinderException ex)
            {
                Console.WriteLine("Exception : " + ex.Message);
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {            
            Logger logger = new Logger();

            Customer customer = new Customer
            {
                Seq = 1,
                Name = "Boram"
            };

            Company company = new Company
            {
                Seq = 6,
                Name = "NanoSoft"
            };

            Project project = new Project
            {
                Seq = 108,
                Name = ".NET 99.9"
            };

            NoneSeqThing noneSeqThing = new NoneSeqThing
            {
                Name = "None Seq Thing"
            };

            object[] entities = new object[] { customer, company, project, noneSeqThing };

            foreach (object entity in entities)
            {
                logger.WriteLog(entity);
            }
        }
    }
}

 

위에서는 몇가지 객체를 선언해서 DTO를 흉내냈고, Seq가 있는 타입과 없는 타입이 있는 것을 볼 수 있습니다. 그리고 ChangeSet에서 반환되는 객체를 그냥 dynamic으로 Logger에게 넘겨주고 있습니다. 그리고 Seq멤버변수가 있으면 남기고 없으면 안남기는 식으로 다른 처리를 해주고 있습니다. 그리고 결과는 아래와 같습니다.



그렇습니다, 이런식으로 dynamic을 이용한다면 지난 포스트에서 Paul Graham아자씨가 이야기 했던 나이들고 엄한 이모 컴파일러와 좀더 편하게 대화할 수 있는 법이죠. 써놓고 보니깐 포스트가 별거 없네요. 기분 탓이 아니라 제 내공 탓이겠죠. 여담이지만, 제가 해결한 방식은 리플렉션으로 Seq프로퍼티를 검색해서 유/무를 판별하는 방식을 이용했습니다. 확실히 손이 좀더 많이 가는 방법이죠.

이렇게 여러가지 패러다임에 익숙해지고 잘 활용할 수 있게 되면, 점점 더 빠르고 경쾌한 리듬으로 코드를 작성하는 일도 가능하지 않을까 생각해봅니다. 이제 프로그래밍을 하시는 분들도 세계화에 발맞추는 속도에 걸맞게 프로그래밍언어에 대해서도  ployglot이 되어야 하지 않을까 생각합니다. 다만, 제 생각엔 그러한 노력은 프로그래머에게 족쇄를 채우는게 아니라 오히려 더 큰 자유를 주는 일이라고 생각합니다. 그럼, 또 개발하다가 dynamic의 헬프가 필요한 순간이 오면 나누도록 하겠습니다.

여담이지만, polyglot하니깐 중학교때 "핸들 이빠이 돌려"라는 한문장으로 3개국어를 자유자재로 구사하시던 한문선생님이 떠오르는 군요. ㅋ


-참고자료
1. Pro LINQ: Language Integrated Query in C# 2008, Joseph C. Rattz, Jr. , APRESS.

신고