Welcome to Dynamic C#(16) - dynamic이라도 이건 안되는 거임.

C# 2010. 5. 5. 09:00 Posted by 알 수 없는 사용자
- 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