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

C# 2010. 1. 20. 09:00 Posted by 알 수 없는 사용자
- 제목이 표절인거 같은데...?

넵. 존경해 마지 않는 스캇 펙의 아직도 가야할 길을 요즘 감명깊게 읽고 있기 때문만은 아니구열. 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