Welcome to dynamic C# 외전(3) - 감시하는 자와 감시당하는 자.

C# 2009. 12. 19. 09:00 Posted by 알 수 없는 사용자
오늘도 하루가 밝았어요.
상쾌한 기분으로 기지개를 켜고, 커튼을 젖혔어요.

오~ 마이~갓.
바깥에 왠 검은 외투를 입은 사람들이 망원경 뒤집어쓰고 이쪽을 째려보고 있어요.
아침부터 기분이 좋지 않아요.
자고일어나니 스타가 된걸까요, 왜 이쪽을 째려보는 건지 도무지 모르겠어요.
집밖을 나섰어요.
대놓고 따라와요.
집밖을 나왔는데도 왜 망원경은 계속 뒤집어 쓰고 있는건지 모르겠어요.
- [감시당하는 자]의 탐구생활


- 시작부터 탐구생활드립이냐.

그냥 한번 해보고 싶었을 뿐이니 노여워 마시길 바랍니다. 오늘은 감시하는 자와 감시당하는 자의 이야기를 해보려고 합니다. 즉, Observer패턴을 말하는 건데요. 뭔가 변화가 있을때 그 변화를 바로 알아차려서 어떤 동작을 수행해야 할때. 변화의 대상을 유심히 관찰하고 있어야 겠죠. 근데, 유심히 관찰하고 있는거 보다 변화가 일어났음을 알려주는 친구가 있다면 더 편리하겠죠. 이벤트 모델이 그중의 한 예 입니다. 이벤트가 일어났을때 통지를 받겠다고 등록을 해두면, 이벤트 발생시에 이벤트를 기다리는 모든 객체들에게 이벤트가 발생했음을 통지해줘서 적절한 조치를 취하도록 해주는 것 처럼 말이죠.

이런 건, 기존에는 직접 패턴을 통해 작성을 해야 했지만 닷넷 프레임워크 4.0에 이런 Observer패턴을 지원하기 위한 새로운 인터페이스가 추가 되었습니다. 거기에 대해서 설명을 드려볼까 합니다. 기존의 포스트와 마찬가지로 이번 포스트 역시 눈을 조심하시고 언제든지 OME를 외칠 준비를 하시길 바랍니다.



- 기존의 방식으로.

닷넷 프레임워크 4.0이전에는 어떻게 만들어야 했는지 한번 알아보겠습니다. 제 이해가 부족해서 적절치 못한 예제와 적절치 못한 수준일 수도 있으니, 마음의 준비 하시구요. 우선, UML 다이어그램을 한번 보시죠.

- 그림1. 예제의 UML 다이어그램.(인터페이스가 클래스모양을 하고 있는 것 같지만, 착시현상입니다. 이해를 위해서 부족한 실력이지만 그려봤습니다-_-)

네, 그림에서 느낌이 오듯이 라디오방송국과 청취자를 모델링해봤습니다. 제가 만드는거 중에 쓸모있는 건 별로 안나오죠-_-. 우선 RadioBroadcaster라는 인터페이스가 있구요, KBRRadioBroadcaster가 그걸 상속해서 방송국의 역할을 합니다. 그리고 각각의 Listener가 방송국에 주파수를 맞추고, 전달되는 전파를 수신하는 형태이구요.

enum BroadcastStatus
{
    StandBy = 0,
    OnAir = 1,
    End = 2
}

interface RadioBroadcaster//지켜볼 대상
{
    bool ListenTo(Listener listener);
    bool QuitFrom(Listener listener);
    void DoBroadcasting();
    void StartBroadcast();
}


네, 일단 방송의 상태를 알려주는 열거자와 지켜볼 대상이 되는 인터페이스입니다.


class KBRRadioBroadcaster : RadioBroadcaster//정보의 공급자
{
    private List<Listener> listeners = new List<Listener>();
    public BroadcastStatus broadcastStatus = BroadcastStatus.StandBy;

    private List<string> scripts = new List<string>
    {
        "안녕하십니까. KBR방송국의 김병만 기자입니다.",
        "요즘 살림살이 초큼 어찌 나아지셨슴까?",
        "안 나아지셨다구요? 그럼 이 방송에서 취업 필살전략을 알려드립니다.",
        "요즘같이 취업이 어려운때 3개 국어가 가능하면 취업 직빵이겠죠.",
        "따라 해보시져, Handle いぱい 꺽어.(핸들 이빠이 꺽어).",
        "3개국어 정말 쉽져? 이것만 한다면 당신도 취업계의 슈퍼스타!"
    };

    private int currentScriptIndex = 0;

    public bool ListenTo(Listener listener)
    {
        try
        {
            listeners.Add(listener);
        }
        catch (Exception ex)
        {
            return false;
        }

        return true;
    }

    public bool QuitFrom(Listener listener)
    {
        if (listeners.Contains(listener))
        {
            listeners.Remove(listener);
            return true;
        }

        return false;
    }

    public void DoBroadcasting()
    {
        if (scripts.Count.Equals(currentScriptIndex))
        {
            Console.WriteLine("방송끗.");
            broadcastStatus = BroadcastStatus.End;
        }
        else
        {
            foreach (Listener listener in listeners)
            {
                listener.Listening(scripts[currentScriptIndex]);
            }
            currentScriptIndex++;
        }
    }

    public void StartBroadcast()
    {
        broadcastStatus = BroadcastStatus.OnAir;
    }
}


그리고 방송국입니다. 듣고 싶은 청취자는 ListenTo메서드를 통해 주파수를 맞추고 QuitFrom을 통해 라디오를 끕니다. 그리고 DoBroadcasting을 통해 정해진 대사가 한번에 하나씩 전파를 통해 나가구요.


class Listener//지켜보는 행위의 주체
{
    private string name;

    public Listener(string name)
    {
        this.name = name;
        Console.WriteLine("나는야 {0}. 라디오를 가진 남자지.",this.name);
    }

    public void ListenToKBRRadio(RadioBroadcaster radioBroadcaster)
    {
        bool status = radioBroadcaster.ListenTo(this);

        if (status)
        {
            Console.WriteLine("{0} : 수신감도 조쿠나. ㅎㅎㅎㅎ", this.name);
        }
        else
        {
            Console.WriteLine("{0} : 아놕. 라디오도 못듣능구나", this.name);
        }
    }

    public void QuitFromKBRRadio(RadioBroadcaster radioBroadcaster)
    {
        bool status = radioBroadcaster.QuitFrom(this);

        if (status)
        {
            Console.WriteLine("{0} : 아놔. 완전 재미없엉", this.name);
        }
        else
        {
            Console.WriteLine("{0} : 아. 듣고 있던거 아니구낭", this.name);
        }
    }

    public void Listening(string script)
    {
        Console.WriteLine("{0} : {1}", this.name,script);
    }
}


그리고 이번엔 지켜보는 자, 청취자입니다. 라디오를 청취하고, 끊을 수 있으며, Listening을 통해서 매번 날아오는 전파를 수신합니다.

class Program
{
    static void Main(string[] args)
    {
        Listener listener1 = new Listener("마논개");
        Listener listener2 = new Listener("코턱용");
        Listener listener3 = new Listener("송순신");

        KBRRadioBroadcaster kbrRadioBroadcaster = new KBRRadioBroadcaster();
        listener1.ListenToKBRRadio(kbrRadioBroadcaster);//마논개 라디오 청취시작.
        int count = 0;
        do
        {
            if (count == 0)
            {
                kbrRadioBroadcaster.StartBroadcast();
            }
            if (count == 2)
            {
                listener2.ListenToKBRRadio(kbrRadioBroadcaster);//코턱용 라디오 청취시작
            }
            if (count == 3)
            {
                listener1.QuitFromKBRRadio(kbrRadioBroadcaster);//마논개 라디오 끔
                listener3.ListenToKBRRadio(kbrRadioBroadcaster);//송순신 라디오 청취시작
            }
            kbrRadioBroadcaster.DoBroadcasting();
            count++;
            Thread.Sleep(500);
        } while (kbrRadioBroadcaster.broadcastStatus != BroadcastStatus.End);
       
    }
}


그리고 프로그램 코드지요. 마논개, 코턱용, 송순신 세명이 라디오를 청취하는데요. 처음에는 마논개 혼자 듣다가, 두번이후에 코턱용이 라디오를 듣기 시작합니다. 그리고 세번째가 되면, 마논개가 라디오가 지겨워서 끄구요, 송순신이 라디오를 듣기 시작합니다. 방송이 끝날때까지, 0.5초당 한번씩 전파를 타고 방송이 날아오구요. 아웃사이더쯤되야 가능한 속도겠네요. 실행화면을 보시면 아래와 같습니다.

-그림2. 실행한 모습!

넵, 의도했던대로 방송이 전파를 타고 나오면서 사람들이 방송을 들었다가 빠집니다. 대사가 다 나온후에 방송이 종료되구요. 제 내공의 문제로 정확한 Observer패턴의 형태나, 사용방법이라고 볼 수는 없겠지만 뭐 이해에는 문제가 없을 것 같습니다. 하하하-_-;;;


- 이제 새로운 걸 내놔봐.

넵. 이젠 닷넷 4.0에 추가됐다는 새로운 인터페이스 IObservable<T>와 IObserver<T>에 대해서 말씀드려보도록 하겠습니다. IObserable<T>는 지켜볼 대상이 되는 클래스가 구현을 할 인터페이스입니다. 위에서 보자면 방송국이 되겠죠. 그리고 IObserver<T>는 지켜보는 행위를 하는 클래스가 구현을 할 인터페이스입니다. 마찬가지로 청취자가 되겠죠. 즉 IObservable<T>를 구현하는 클래스는 말그대로 observable한(지켜볼 수 있는) 클래스를 말하는 거구요, IObserver<T>를 구현하는 클래스도 말 그대로 observer클래스(지켜보는)가 되는 거죠. 그럼, 위의 예제를 여기에 맞춰서 옮겨 볼까요~~~~! 그전에, 일단 다이어그램을 한번 보시져.

-그림3. 새로운 인터페이스를 활용한 다이어그램


enum BroadcastStatus
{
    StandBy = 0,
    OnAir = 1,
    End = 2
}

interface RadioBroadcaster : IObservable<string>//지켜볼 대상
{       
    void DoBroadcasting();
    void StartBroadcast();
}


RadioBroadcaster가 IObservable<string>을 상속하고 있죠? 지켜볼 대상이 되기 때문이죠. 그리고 string은 지켜보는 observer가 전달받는 데이터를 말하는데요, 방송국에서 string타입의 전파를 뿌렸으므로 string이 됩니다.

class KBRRadioBroadcaster : RadioBroadcaster//정보의 공급자
{
    private List<IObserver<string>> listeners = new List<IObserver<string>>();
    public BroadcastStatus broadcastStatus = BroadcastStatus.StandBy;

    private List<string> scripts = new List<string>
    {
        "안녕하십니까. KBR방송국의 김병만 기자입니다.",
        "요즘 살림살이 초큼 어찌 나아지셨슴까?",
        "안 나아지셨다구요? 그럼 이 방송에서 취업 필살전략을 알려드립니다.",
        "요즘같이 취업이 어려운때 3개 국어가 가능하면 취업 직빵이겠죠.",
        "따라 해보시져, Handle いぱい 꺽어.(핸들 이빠이 꺽어).",
        "3개국어 정말 쉽져? 이것만 한다면 당신도 취업계의 슈퍼스타!"
    };

    private int currentScriptIndex = 0;

    public void DoBroadcasting()
    {
        if (scripts.Count.Equals(currentScriptIndex))
        {
            Console.WriteLine("방송끗.");
            broadcastStatus = BroadcastStatus.End;
        }
        else
        {
            foreach (IObserver<string> listener in listeners)
            {
                listener.OnNext(scripts[currentScriptIndex]);
               
                // Assume that we've arrived at location of Latitude has changed by 4.
                if (broadcastStatus == BroadcastStatus.End)
                {
                    listener.OnCompleted();
                }
            }
            currentScriptIndex++;
        }
    }

    public void StartBroadcast()
    {
        broadcastStatus = BroadcastStatus.OnAir;
    }

    #region IObservable<Listener> Members

    public IDisposable Subscribe(IObserver<string> listener)
    {
        listeners.Add(listener);
        // Announce current location to new observer.
        Console.WriteLine("{0} : 수신감도 조쿠나. ㅎㅎㅎㅎ", (listener as Listener).Name );

        return listener as IDisposable;
    }

    #endregion

    public void UnSubscribe(IObserver<string> listener)
    {
        listeners.Remove(listener);
        Console.WriteLine("{0} : 아놔. 완전 재미없엉", (listener as Listener).Name);
    }
}


변화가 있다면, IObservable의 메서드인 Subscribe를 구현했다는 것과 QuitFrom이 이름을 맞추기 위해서 UnSubscribe로 바뀐건데요. Subscribe는 ListenTo가 그랬듯이 변화가 생기면, 그걸 알려달라고 등록하는 통로가 됩니다.

class Listener : IObserver<string>//지켜보는 행위를 하는 주체
{
    private string name;

    public string Name
    {
        get
        {
            return name;
        }
    }

    public Listener(string name)
    {
        this.name = name;
        Console.WriteLine("나는야 {0}. 라디오를 가진 남자지.", this.name);
    }

    #region IObserver<string> Members

    public void OnCompleted()
    {
        Console.WriteLine("{0} : 음 재밌네 ㅋㅋㅋㅋㅋㅋㅋ", name);
    }

    public void OnError(Exception error)
    {
        Console.WriteLine("{0} : 아놔 감도 완전 좌절이네.", this.name);
    }

    public void OnNext(string script)
    {
        Console.WriteLine("{0} : {1}", this.name, script);
    }

    #endregion
}


그리고 요건 청취자죠. 지켜봐야 하기 때문에 IObserver<string>을 구현한게 보이시죠? 그리고 인터페이스의 메서드를 구현했는데요. 각각 완료시에 할 동작을 담을 OnCompleted와 에러발생시에 처리할 OnError, 그리고 기존의 Listening과 같이 변화를 전달받을때 할 동작을 담을 OnNext로 나눠집니다.


class Program
{
    static void Main(string[] args)
    {
        Listener listener1 = new Listener("마논개");
        Listener listener2 = new Listener("코턱용");
        Listener listener3 = new Listener("송순신");

        KBRRadioBroadcaster kbrRadioBroadcaster = new KBRRadioBroadcaster();
        kbrRadioBroadcaster.Subscribe(listener1);//마논개 라디오 청취시작.
        int count = 0;
        do
        {
            if (count == 0)//방송시작
            {
                kbrRadioBroadcaster.StartBroadcast();
            }
            if (count == 2)//잠시후 코턱용 라디오 청취시작
            {
                kbrRadioBroadcaster.Subscribe(listener2);
            }
            if (count == 3)//잠시후
            {
                kbrRadioBroadcaster.UnSubscribe(listener1);//마논개 라디오 끔
                kbrRadioBroadcaster.Subscribe(listener3); //송순신 라디오 청취시작
            }
            kbrRadioBroadcaster.DoBroadcasting();
            count++;
            Thread.Sleep(500);
        } while (kbrRadioBroadcaster.broadcastStatus != BroadcastStatus.End);
    }
}


그리고 프로그램 코드죠. 동작은 위와 동일합니다. 결과를 보시죠.

-그림4. 동일한 결과


- 마무리.

의의를 찾자면, 프레임워크 레벨에서 Observer패턴을 지원하면서 좀 더 정돈된 형태로 구현을 할 수 있다는 것과 그렇게 구현을 했을때 나중에 프레임워크 레벨에서 성능 개선이 있던지 하면, 그런 혜택을 코드의 변경없이 누릴 수 있다는 점이 있겠구요. 또 한가지 점은 프레임워크나 다른 기술에서 내부적으로도 이 인터페이스를 활용하게 될거고, 그리고 새로운 형태의 프레임워크도 등장을 기대할 수 있다는 점일 것 같습니다.

실제로, 이 인터페이스를 통해서
Reactive Extensions for .NET(줄여서 Rx) 라는 프로젝트가 Devlab에 공개되었습니다. 스크린캐스트를 살짝 들여다보니, 흥미로운 응용이 가능하더군요. 가능하면 다음번에는 Rx를 가지고 이야기를 한번 해보고 싶네요. 그럼, 시력을 떨어뜨리는 글을 열심히 읽어주셔서 감사합니다. 날도 추운데 피드백은 따쓰하게... 아시져? ㅋㅋㅋㅋ 그리고! 소스는 첨부해드립니다.


- 참고자료

1. http://msdn.microsoft.com/en-us/library/dd990377(VS.100).aspx




[JumpToDX11-8] Deferred Contexts

DirectX 11 2009. 12. 2. 09:00 Posted by 알 수 없는 사용자

멀티스레드 기반의 렌더링을 위해서 DirectX11 에서는 세가지를 중점에 두었습니다.

- 비동기적으로 자유스러운 리소스 로딩과 관리.
  ( 이들은 렌더링과 동시에 수행될 수 있어야 한다. )

- 멀티스레드 형식으로 렌더링 커맨드의 생성.
  ( 여러 스레드로 나누어서 렌더링 작업을 할 수 있어야 한다. )

- 디스플레이 리스트( Display lists )의 지원.


첫번째 리소스와 관련한 것을 지난 시간에 알아보았습니다.
이제는 실제 렌더링 작업에 대해 알아보아야 합니다.
그 실제 렌더링 작업을 위해서 우리는 새롭게 등장한 Deferred Context 라는 것을 살펴볼 것입니다.
Deferred Context 가 멀티스레드 기반의 렌더링에서 가장 중요한 키워드입니다.

< Device 의 분리 >
지난 세대의 DirectX는 모든 GPU 관련 내용의 처리는 Device 인터페이스를 통해서 수행했습니다.
즉 지난 회들에서 꾸준히 언급했던 것처럼,
Device 인터페이스를 통해서만 커맨드( Command ) 를 생성할 수 있었습니다.



오직 싱글코어 기반으로 설계된 지난 세대의 DirectX 였기 때문에,
위의 그림과 같은 상황이 연출되었습니다.
이들에 대한 내용은 지난 시간을 통해서 꾸준히 언급되었기 때문에, 더 자세한 언급은 하지 않겠습니다.

Device 인터페이스에 모든 작업이 집중되어 있었기 때문에,
이를 분리할 방법이 필요했습니다.
그 기준은 앞서 언급했듯이, 
그래픽카드에 보내는 작업이 Free threaded 한지였습니다.
결론적으로 얘기 드리면 DirectX11 에서는 기존의 Device 가 분리에 분리를 거듭했습니다.
그래서 아래와 같은 구조로 되었습니다.





DirectX11 에서 이제 개발자가 다루어야하는 커맨드 생성 인터페이스는 총 3가지입니다.
Device 는 Free threaded 한 API 만 사용하는 인터페이스입니다.
주로 리소스들이 포함됩니다.( 버퍼나 텍스쳐, 쉐이더 등 )

Device Context 는 실제로 렌더링과 관련된 인터페이스입니다.
렌더스테이트의 교체나 Draw 명령을 내리기 위해서는
반드시 Device Context 를 통해서 커맨드를 생성해야 합니다.
더 자세한 사항은
http://vsts2010.net/115 여기를 보시기 바랍니다.^^

Device Context 는 다시 두개로 분리될 수 있습니다.
Immediate Context 와 Deferred Context 가 바로 그것들입니다.

만약 멀티스레드 기반으로 렌더링 하고 싶지 않다면,
Deferred Context 는 사용하지 않으셔도 됩니다.
Deferred Context 의 유무가 바로 멀티스레드 기반의 렌더링이냐,
아니면 일반적인 렌더링이냐를 결정
합니다.
반면에 Immediate Context 는 반드시 한개만 존재해야 합니다.
이 인터페이스는 실제로 렌더링과 관련된 커맨드를 생성하기도 하지만,
생성된 커맨드를 그래픽 카드로 보내는 일
도 합니다.

< Deferred Context >

Deferred Context는 애플리케이션에서 여러개 생성될 수 있습니다.
하나의 스레드에 하나씩 Deferred Context 가 사용될 수 있으며, 이들은 Thread unsafe 합니다.
이렇게 하나의 스레드에 할당되어진 Deferred Context는 Display List 를 생성합니다.
이들은 GPU 가 바로 처리 가능한 커맨드들을 모아둔 버퍼라고 할 수 있습니다.
이렇게 Display List 를 미리 만들어둠으로써 성능을 크게 개선 시킬 수 있습니다.
일반적으로, CPU 가 커맨드를 생성시키는 시간이 꽤 오래 걸리기 때문입니다.
( 생성될 커맨드가 변화가 없다면, 이렇게 미리 만들어 두면 크게 도움이 되겠죠? ^^ )


사실 위의 그림은 개념적인 것입니다.
실제 소스레벨에서 Deferred Context 를 가리키는 인터페이스는 별도로 존재하지 않습니다.
Immediate Context 를 가리키는 인터페이스는 ID3D11DeviceContext 입니다.
Deferred Context 를 가리키는 인터페이스도 ID3D11DeviceContext 입니다.
즉, 둘 다 동일한 인터페이스를 통해서 처리되고 있는 것입니다.
실제 멤버 변수 선언들을 조금 나열해 보면 다음과 같습니다.

ID3D11Device*      m_ipGPU;
ID3D11DeviceContext*    m_ipImmediateContext;
ID3D11DeviceContext**  m_ippDeferredContextArray;

동일한 인터페이스 선언을 확인하셨습니까?
하지만 인터페이스가 동일하다고 해서, 이를 동일하게 생각해서는 안됩니다.
동일한 인터페이스를 사용하는 이유는
Deferred Context 는 Immediate Context 의 모든 기능을 지원한다는 의미로 받아들여야 합니다.
결과적으로 Device Context 는 아래의 그림과 같은 구조로 확장될 수 있습니다.




멀티스레드 기반으로 렌더링을 한다는 것은 엄밀히 말해서는 지원하지 않습니다.
정확히 말하자면, 멀티스레드 기반으로 커맨드를 생성해서
이들을 순차적으로 그래픽 카드로 보냅니다.

위의 그림이 이를 잘 표현하고 있습니다.

Deferred Context 는 각각의 스레드에 의해서 DisplayList 를 생성하고,
이들을 그래픽 카드에 보내기 위해 버퍼에 저장합니다.
그리고 실제로 그래픽카드에 커맨드를 보내기 위해서는
반드시 Immediate Context 를 통해야 합니다.
이때 Immediate Context 를 통해서 직접적으로 커맨드를 생성시킬 수 있습니다.

아무래도 렌더링이라는 작업은 순간순간의 렌더링 상태들에 의해서
결과가 변하기 때문에, 최종적으로 전송하는 작업만큼은 순차적으로 설계한 듯 합니다.
이로써 우리는 Deferred Context 를 사용할 준비가 되었습니다.^^



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

C# 2009. 11. 30. 08:30 Posted by 알 수 없는 사용자

- 또 외전이군 ㅋ

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

* 경고 : 늘 그렇지만 제가 하는 포스팅은 허접합니다. 왜냐면, 제가 허접하기 때문이죠. 포스팅의 허접함에 눈이 아프시다면, "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

[JumpToDX11-7] 간편해진 리소스 처리.

DirectX 11 2009. 11. 18. 15:00 Posted by 알 수 없는 사용자


이번 회의 설명을 위해서 API 를 다음과 같이 크게 나누어 보았습니다.

·        Thread unsafe API

·        Thread safe API

·        Free threaded API


Thread unsafe 한 경우에는 멀티스레드들이 동시에 호출할 수 있지만,
이들에 대한 안정성에 대해서는 전혀 보장하지 않습니다.
결과가 갑자기 이상할 수도 있고, 예측 불가능한 상황을 연출할 수도 있습니다.
주로 즉시즉시 상황을 만들어야 할때 사용될 수 있을 것입니다.

Thread safe 한 API 는 어떤 동기화 모델에 의해서 API 가 보호되어집니다.
여러 스레드에서 API 를 호출하더라도, 그들의 안정적인 동작을 보장한다는 의미입니다.
동기화 작업이 별도로 있기 때문에 느려질 수 있다는 단점이 있습니다.

Free threaded 한 API 는 여러 스레드에서 호출을 하더라도,
어떠한 동기화 모델 없이도 API 가 안정적으로 동작한다는 것을 의미합니다.
그렇기 때문에 아주 높은 성능 향상을 얻을 수 있습니다.

< 기존의 리소스 처리 >

멀티스레드 기반의 렌더링으로 가는 첫번째 길은 리소스 부분입니다.
PC 가 발전하면서 성능적으로나 용량적으로 많은 발전을 거듭했습니다.
더블어 게임에서 사용되는 리소스의 용량도 크게 증가했습니다.

멀티스레드 처리가 어느 정도 자리를 잡으면서,
현재 많은 게임들은 이들 리소스에 대한 처리를 별도의 스레드에서 처리하기도 합니다.




일반적인 텍스쳐 로딩 상황을 한번 가정해 보았습니다.
가장 먼저 로더 스레드가 특정한 파일을 읽습니다.
읽어둔 파일을 바탕으로 실제 메모리를 할당하기 위해서는 결국 Device 인터페이스를 통해야 합니다.
DirectX 에서 리소스 관련 메모리를 할당하는 것은
Device 인터페이스를 통해서만 가능하기 때문입니다.
커맨드(Command) 를 생성하는 권한은 오직 Device 인터페이스만이 가지고 있습니다.
( 지난 회를 참고하시기 바랍니다. )

멀티스레드 기반으로 리소스를 로딩하고 있지만,
실제로는 많은 부분이 메인스레드( 여기서는 Render Thread )에 의존하여 작업이 수행되고 있습니다.
리소스를 생성하기 위해서는 반드시 Create, Lock & Unlock 작업이 필요합니다.

< 여기서 잠깐...>
사실 위의 그림의 경우에는 일반적으로 사용되는 구조로 보기 어렵습니다.
그림은 거의 순차적인 처리 구조이기 때문에,
이런 경우라면 굳이 저렇게 사용할 필요가 없습니다.
실제로 많은 게임들은 텍스쳐가 아직 로딩 중이라면,
Default-Material 을 적용해서 폴리곤을 렌더링하도록 구현하기도 합니다.
즉, Render Thread 가 쉴새 없이 계속 커맨드를 생성하는 구조입니다.^^
그리고 위의 예는 간단한 설명을 위한 것입니다.
개발자분들마다 개성 강한 여러 방법들이 존재하시겠죠? ^^

 

 < 간편해진 리소스 관련 커맨드 생성 >

우리가 DirectX 에서 사용하는 대부분의 리소스 작업은 사실 Free Threaded 합니다.
리소스 처리를 위해서 특별한 동기화 처리가 API 레벨에서는 필요하지 않습니다.
( 개발자의 몫입니다. )
그런데 위의 그림처럼 그 동안은 커맨드를 생성해주는 인터페이스가 오직 하나였기 때문에
메인 스레드에 의존해서 처리할 수 밖에 없었습니다.

두번째 시간에 저는 Device와 DeviceContext 에 대해서 설명을 드렸습니다.
기억이 나지 않으시는 분들은 다시 살펴보시기 바랍니다.^^
요약해드리자면, DirectX11 에서는 Free threaded 한 API 와 그렇지 않은 API 들을 분리했습니다.
Free threaded 한 부분에 바로 리소스 처리가 있습니다.
앞서 우리는 Free threaded 의 이점에 대해서 짧게 언급했었습니다.

위의 그림에서,
이제는 Loader therad 는 굳이 메인 스레드( Render thread ) 에 의존하지 않아도,
순차적으로 리소스 관련 커맨드들을 생성시킬 수 있습니다.
커맨드들이 생성되는 순서는 Free threaded 한 경우에는 중요하지 않기 때문에,
빠른 속도로 처리
할 수 있습니다.
특히나, 메인스레드가 별도로 다른 작업을 수행해서 커맨드를 생성시키는 작업이 있다고 해도,
그것과는 별도로 커맨드 생성 작업을 수행할 수 있습니다.
( 커맨드 생성을 위한 대기 시간이 짧아졌다고 할 수 있습니다. )
이로써, 이제는 조금 더 멀티스레딩 형식에 가까워졌다고 할 수 있습니다.

리소스 처리를 이렇게 분리하는 것은
멀티스레드 기반으로 렌더링으로 가는 중요한 기반을 제공
해 주었습니다.

< 다음 회에서는.... >
다음 회부터는 이제 멀티스레드 기반으로 렌더링 관련 커맨드를 생성하는 것을 살펴볼 것입니다.


구름 속의 미래 : Windows® Azure™ Platform [2]

Cloud 2009. 10. 11. 21:52 Posted by 알 수 없는 사용자
안녕하세요? Ted 입니다.
이번에는 PT 로 자료를 준비했습니다.

클라우드에 대해 다시 살펴보고 Azure 를 실제로 사용해 보도록 하겠습니다.

- 이번엔 웹디자이너와 배포에 대해서

부족한 번역이지만, 이번엔 웹 디자이너와 배포에 대해서 적어볼까 합니다.


- Visual Studio 2010 Web Designer Improvement

 

Visual Studio 2010의 웹페이지 디자이너는 더 뛰어난 CSS 호환성, HTML ASP.NET 마크업의 코드조각에 대한 추가적인 지원과 Jscript를 위해서 다시 디자인된 인텔리센스등으로 더 향상된 기능을 제공합니다.

 

 

- Improved CSS Compatibility

 

Visual Studio 2010 Visual Web Developer 디자이너는 CSS 2.1 표준과 호환되는데요. 이 디자이너는 HTML 소스와의 통합성을 높이면서 기존의 버전의 Visual Studio보다 전체적으로 더 견고한 기능을 제공합니다. 내부적으로는 앞으로 추가될 렌더링, 레이아웃, 사용성 및 유지보수를 위한 설계적인 측면에서의 향상도 있었습니다.

 

 

- HTML and Jscript Snippets

 

HTML에디터는 인텔리센스를 이용해서 태크이름을 자동완성합니다. 그리고 인텔리센스의 코드조각 기능이 전체 태그와 기타 부분을 완성합니다. VisualStudio 2010에서는 인텔리센스의 코드조각 가능이 C# Visual Basic에 적용되었던 것 처럼 Jscript에 대해서도 지원될 것입니다.

 

VisualStudio 2010에는 200개 이상의 코드조각이 ASP.NET HTML의 일반적인 태그와 필요한 속성(runat=”server” 같은)과 특정 태그들에 공통적인 속성들(ID, DataSourceID, ControlToValidate, Text같은)의 자동완성을 도와줍니다.

 

추가적인 코드조각들을 다운받을 수 있으며, 팀의 공동작업이나 개인작업에 공통으로 쓰이는 마크업의 블록을 코드조각으로 뽑아내서 사용할 수도 있습니다.

 

 

- Jscript IntelliSense Enhancements

 

VisualStudio 2010에선 더 풍부한 편집을 위해서 Jscript의 인텔리센스가 다시 디자인 되었습니다. 이젠 인텔리센스가 registerNamespace같은 메서드나 기타 비슷한 자바스크립트 프레임워크의 기술을 이용해서 동적으로 생성되는 객체를 인식할 수 있습니다. 그리고 방대한 스크립트 라이브러리를 분석하고 화면에 인텔리센스를 표시하는데 걸리는 시간을 사용자가 거의 못 느낄 정도로 향상시켰습니다. 호환성 역시 대단히 향상되어서 외부 라이브러리나 다양한 코딩 스타일도 지원할 수 있게 되었습니다. 문서화를 위한 주석은 타이핑하는 즉시 파싱되어서 인텔리센스에서 바로 활용할 수 있습니다.

 

 

- Web Application Deployment with Visual Studio 2010

 

현재 웹 어플리케이션을 배포하는 작업은 우리가 꿈꾸었던 것만큼 쉽지 않았죠. ASP.NET 개발자는 종종 아래와 같은 문제에 직면하곤 합니다.

 

  FTP같은 기술을 사용해야 하는 공동 호스팅 사이트에 배포하는 경우. 게다가 데이터베이스를 세팅하기 위한 SQL스크립트를 직접 실행해야 하거나 가상 디렉토리같은 IIS세팅도 바꿔야 한다.

  엔터프라이즈 환경에서는 웹 어플리케이션을 배포하는 것으로 끝나지 않고, ASP.NET 설정파일이나 IIS세팅도 수정해야 한다. 데이터베이스 관리자는 데이터베이스를 돌리기 위해 필요한 SQL스크립트를 돌려야 한다. 그런 설치작업은 종종 몇 시간을 잡아먹으며 노동에 가까운 일이 되며, 세세하게 문서화 되어야 한다.

 

VisualStudio 2010은 웹 어플리케이션을 중단없이 배포할 수 있게 도와주는 새로운 기술을 통해서 이런문제들을 해결합니다. 이런 기술들중의 하나가 IIS Web Deployment Tool(MsDeploy.exe)이죠.

 

VisualStudio 2010에서 웹배포에 관련된 요소들은 아래와 같이 크게 분류할 수 있습니다.

 

  Web packaging

  Web.config Transformation

  Database deployment

One-Click Publish for Web applications

 

아래의 섹션들에서 하나씩 자세하게 설명을 해보겠습니다.

 

 

Web Packaging

 

VisualStudio 2010에서는 MSDeploy 툴을 사용해서 어플리케이션을 압축해서 압축파일(.zip)을 생성하는데, 그 파일을 웹 패키지라고 부릅니다. 패키지파일은 어플리케이션에 대한 메타데이터와 아래의 내용들을 포함합니다.

 

 어플리케이션 풀 세팅과 에러페이지 세팅, 등등을 포함하는 IIS세팅

  웹페이지와 사용자 정의 컨트롤, 정적인 컨텐츠(이미지와 HTML파일), 등등을 포함하는 실제 웹 컨텐츠

  SQL 서버의 데이터베이스 스카마와 데이터

  보안 인증서, GAC에 설치할 컴포넌트, 레지스트리 세팅, 등등

 

웹 패키지는 아무 서버에나 복사할 수 있고, IIS 매니저를 사용해서 수동으로 설치할 수 있습니다. 또는, 커맨드라인 명령어나 배포API를 사용해서 자동으로 배포할 수도 있습니다.

 

VS2010에서는 웹패키지를 만들기 위해서 사용가능한 MSBuild task target을 제공합니다. 더 많은 정보는 Vishal Joshi의 블로그에 있는 10 + 20 reasons why you should create a Web Package를 참조하시길 바랍니다.

 

 

Web.config Transformation

 

웹 어플리케이션의 배포를 위해서 VisualStudio 2010에서는 XML Document Transform (XDT)가 도입되었는데, 이 요소는 개발설정에서 Web.config를 읽어와서 제품설정(production setting)으로 변환하게 도와줍니다. 변환에 관련된 세팅은 변환파일인 web.debug.config, web.release.config, 그리고 기타파일(이 파일들의 이름은 MSBuild 설정과 매치된다)등에 명시되어 있습니다. 변환파일에는 Web.config파일을 배포하기 위해 필요한 수정사항들만 포함되어 있습니다. 이런 수정사항들을 간단한 문법으로 명시해주면 되는거죠.

 

아래의 예제는 배포의 릴리즈설정에 의해 생성가능한 web.release.config파일의 일부분인데요. 예제의 내용중에서 Replace키워드를 보면, 배포과정에서 Web.config파일의 connectionString 노드의 값이 어떻게 바뀌어야 하는지 명시해주고 있습니다.

 

<connectionStrings xdt:Transform="Replace">

  <add name="BlogDB" connectionString="connection string detail]" />

</connectionStrings>

 

더 자세한 정보는, Vishal Joshi의 블로그의 Web Deployment: Web.Config Transformation를 보시면 됩니다.

 

 

- Database Deployment

 

VisualStudio 2010의 배포 패키지에는 SQL 서버 데이터베이스에 대한 의존성 역시 포함될 수 있습니다. 패키지 정의의 일부분으로 원본 데이터베이스의 연결문자열을 명시해줄 수 있습니다. 웹 패키지를 만들때 VisualStudio2010에 의해서 데이터베이스 스키마와 선택적으로 데이터에 대해서 SQL 스크립트가 만들어지고 패키지에 포함됩니다. 사용자 정의 SQL 스크립트를 만들어서 서버에서 순차적으로 실행되어야 할 순서를 지정해줄 수도 있습니다. 배포할때, 타겟 서버에 대한 연결문자열을 제공하고, 배포 프로세스에서 이 연결문자열을 이용해서 해당 데이터베이스에 SQL 스크립트를 실행해서 데이터베이스 스키마를 만들고 데이터를 추가합니다.

 

추가적으로, One-Click Publish를 이용하면 어플리케이션이 원격의 공용 호스트에 게시된 후에 데이터베이스를 직접적으로 게시할 수 있도록 배포를 설정할 수 있습니다. 자세한 내용은 Vishal Joshi의 블로그의 Database Deployment with VS 2010를 참조하시면 됩니다.

 

 

- One-Click Publish for Web Application

 

VisualStudio 2010에서는 IIS 원격 관리 서비스를 이용해서 원격의 서버에 웹 어플리케이션을 게시할 수 있도록 도와줍니다. 게시를 위해서 호스팅 계정이나 테스트 서버나 스테이징 서버를 위한 게시 프로파일(publish profile)을 생성할 수 있습니다. 각각의 프로파일은 안전하게 계정정보를 저장할 수 있구요. 그러면 Web One Click Publish 툴바를 이용해서 한번의 클릭으로 타겟서버에 배포할 수 있게 됩니다. VisualStudio2010에서는 MSBuild 커맨드 라인을 통해서도 배포할 수 있는데요. 이 기능을 통해서 지속적인 통합 모델을 이용하는 팀 빌드 환경에 게시작업을 포함할 수 있습니다.

 

더 자세한 정보는, Vishal Joshi의 블로그의 Web 1-Click Publish with VS 2010를 보시면 되구요, VisualStudio 2010에서의 웹 어플리케이션 배포에 대한 비디오 영상을 보려면, Vishal Joshi의 블로그의  VS 2010 for Web Developer Previews를 보시면 됩니다.

 

Welcome to Dynamic C#(8) - DLR이 나무를 사랑하는 이유

C# 2009. 9. 21. 09:00 Posted by 알 수 없는 사용자

- 아아... 이거슨 Love Story.

오늘 말씀드릴 내용은 DLR이 왜 Expression Tree(이하 표현나무)를 처음에는 박대하다가 나중에 가서는 "역시 난 니가 아니면 안되나봐 ㅠ_ㅠ"같은 대사를 날리게 되는지 Jim Hugunin의 증언을 통해서 알려드리고자 합니다. 그럼 지금부터 달콤 쌉싸름하고 무슨말인지도 잘 모르겠는 러브스토리를 한번 감상해보시져.


- 누군가가 필요하긴 했어. 하지만 너 아닌 다른 나무라도 괜찮을 거 같았어.

 지난 시간에 말씀드렸듯이 C# 3.0에 표현나무가 추가된 이유는 쿼리가 실행되는 곳이 어디냐에 따라서 최적화를 하기 위해서 컴파일된 IL코드의 형태보다는 자료구조의 형태가 훨씬 수월하기 때문이었습니다. 그리고 DLR은 닷넷 프레임워크에서 실행되는 동적 언어들이 공동으로 사용할 수 있는 기반을 제공해주고, 각 언어들끼리 그리고 정적언어들과도 서로 대화를 주고 받을 수 있도록 해주기 위한 장치인데요. 그렇다면, 언어별로 내부적인 구현의 차이점이 있을 수 있겠고 한 언어에서 작성한 코드를 다른 언어에서도 사용가능하려면, 둘 사이에 변환과정이 있어야 하겠고 그 변환과정을 수월하게 해줄 수 있는 도구가 필요하겠죠. 어떤 가요? DLR과 표현나무의 러브스토리의 윤곽이 좀 잡히시나요?

Jim Hugunin에 따르면 애초에 DLR을 설계할때 코드를 표현할 공통자료구조로 트리형태를 생각하고 있었다고 합니다. 그리고 타입이 없으면서 런타임에 late-bound되는 노드들로 구성된 트리로 구현을 시작했다고 합니다.  그래서 그 당시에 IronPython에 맞게 구현된 트리에는 타입이 있는 노드가 ConstantExpression딱 하나였다고 합니다. 상수 값이면 뭐든지 가지고 있는 노드 말이죠. 그리고 자신들이 생각하고 있는 트리와 표현나무 사이에는 공통점이 없다고 생각했었다고 합니다. 왜냐면 정적언어에서 쓰는 게 동적언어에는 안 맞을거라고 생각했기 때문이라는 군요. 그래서 독립적으로 트리를 구성해 나갔다고 하네요.


- 하지만, 다른 나무들을 만날 수록 니가 그립더라.

하지만, 다른 언어의 구현을 추가하면서, 기존의 DLR트리에 각 언어의 특징을 제대로 표현하기 힘들어졌다고 하는군요. 각 언어의 특징을 지원하기 위한 방법이 필요한데, 그렇다고 해서 직접적으로 그 언어의 특징을 위한 노드를 추가할 수 는 없었다네요. 아마도 DLR트리는 DLR에서 도는 언어들이 공통적으로 공유하게되는 자료구조인데, 특정언어에만 있는 노드를 추가하면, 점점 지저분해 지기 때문이겠죠.

예를 들면, Python의 print문을 보면, 내부적으로 print문은 Python에서 출력에 대한 걸 어떻게 처리할지에 대해 알고 있는 static 메서드를 호출한다고 하는군요. 그래서 DLR트리에 static 메서드 호출노드를 추가했다고 합니다. 그러면 print호출은 static 메서드를 호출하는 걸로 연결할 수 있으니까요.

그리고 이런 작업을 하다보니깐, 그동안 개발팀이 추가해오던 노드들이 정적인 타입이 적용되는 표현나무의 각 노드랑 딱 들어맞는 다는 걸 깨달았다고 합니다. 그래서 처음부터 다시 만드는 대신에 기존에 잘 쓰고 있는 걸 확장하자고 마음을 먹었고, 기존의 표현나무에 동적인 연산이나 변수, 이름 바인딩, 흐름제어등을 위한 노드를 추가했다고 합니다.

그리고 DLR에서 동작하는 언어가 이런 트리형태를 만들어 내게 되면서 각각의 언어에 맞는 최적화를 수행하는게 수월해졌습니다. 지난 포스트에서 설명드렸던 표현나무를 사용하면서 얻는 장점과 매우 동일한 점입니다. 그래서 각 언어의 컴파일러는 코드를 표현나무의 형태로 만들어서 그 표현나무를 DLR에게 전해준다고 하는 군요.


- 그들은 현재 잘 살고 있답니다.

Jim Hugunin의 PDC2008의 "Dynamic Languages In Microsoft .NET"의 슬라이드를 통해서 각 언어별로 나타나는 표현나무의 모양을 보도록 하겠습니다.

C#에서 구현한 팩토리얼을 표현나무로 옮긴다면 아래와 같다고 하는군요.



그리고 이걸 dynamic을 써서 C#에서 작성하면 아래와 같다고 합니다. 붉은 색으로 변화가 있는 부분이 있죠? 이 부분이 동적인 타입을 적용한 부분인데요, 즉 "==", "*", "-"같은 DLR의 표준메세지 객체를 이용해서 모든 언어에서 사용가능하도록 하면서 C#에서 왔다는 걸 표시해서 C# 바인더를 사용할 수 있게 한다고 합니다.



1과 2를 비교해보시면 어떤 노드가 추가됐는지 확일 할 수 있군요. 그리고 동일한 코드로 IronPython에서 만든 표현나무는 아래와 같습니다.



C#대신에 Python바인더를 사용하고 있구요, 메서드 호출부분에서 global field를 참조하는 부분이 있는데요, 이 부분은 닷넷의 프로퍼티를 통한 static field와 거의 완벽하게 들어 맞는다고 하는군요. 그래서 약간의 코드만 추가하면 된다고 합니다. 그리고 아래는 루비의 표현나무입니다.



루비는 함수형언어 개념의 표현식을 기반으로 하는데요 구문이라는게 없고 모든 루비의 코드 한라인 한라인은 값을 리턴하는 구조라고 하는군요. 그래서 return 같은 구문을 안써도 되므로 표현나무와 자연스럽게 어울린다는 군요.

즉, 각 언어에서 나온 트리가 모양이 조금씩 다르긴 한데 전체적으로 비슷한 구조를 유지하고 있습니다. 이런 각 언어에서 나온 표현나무를 분석해서 모든 트리에서 공유하는 폼으로 만들 수 있다는 군요. 아직 어떻게 정확하게 그렇게 할 수 있는지는 명확하게 설명된게 없어서(혹은 제가 못찾아서-_-, 제가 몰라서-_-) 더 설명드리기는 조금 힘들거 같군요.


- 마치면서

확실히 제게는 조금 벅찬 주제일 수도 있지만, 제가 이해한 범위내에서 최대한 명쾌하게 설명드리려고 노력했습니다. 하지만 틀린부분이 있을 수 있겠죠. 그렇다면 여러분의 지식을 공유하시면서 따쓰한 피드백을 주시기 발합니다. 캬캬캬....


- 참고자료

1. http://blogs.msdn.com/hugunin/archive/2007/05/15/dlr-trees-part-1.aspx
2. http://channel9.msdn.com/pdc2008/TL10/

- 이어서 이어서!

지난 시간에 이어서 Dynamic Data에 관련된 기능들을 보시겠습니다.



- Entity Templates

 

Entity Templates를 이용하면 사용자 정의 페이지를 만들지 않고도 데이터의 레이아웃을 사용자가 편집할 수 있습니다. 페이지 템플릿은 FormView 컨트롤(기존 버전의 Dynamic Data의 페이지 템플릿에서 사용하던 DetailView컨트롤 대신에) DynamicEntity 컨트롤을 사용해서 Entity templates를 렌더링합니다. 이렇게 하면 사용자는 Dynamica Data가 렌더링한 마크업에 대해서 더 많은 제어를 할 수 있게 되죠.

 

아래의 목록은 Entity templates를 포함하고 있는 새로운 프로젝트 폴더의 구조입니다.

 

\DynamicData\EntityTemplates

\DynamicData\EntityTemplates\Default.ascx

\DynamicData\EntityTemplates\Default_Edit.ascx

\DynamicData\EntityTemplates\Default_Insert.ascx

 

EntityTemplates폴더는 모델 객체들을 어떻게 화면에 표시할지에 대한 템플릿을 담고 있습니다. 기본적으로 객체들은 ASP.NET 3.5 SP1 Dynamic Data에서 생성된 DetailsView의 마크업과 유사한 모양의 마크업을 생성해주는 Default.ascx를 이용해서 렌더링되는데요. 다음의 예제는 Default.ascx 컨트롤의 마크업 입니다.

 

<asp:EntityTemplate runat="server" ID="TemplateContainer1">

  <ItemTemplate>

    <tr

      <td>

        <asp:Label ID="Label1" runat="server" OnInit="Label_Init" />

      </td>

      <td>

        <asp:DynamicControl runat="server" OnInit="DynamicControl_Init" />

      </td>

    </tr>

  </ItemTemplate>

</asp:EntityTemplate>

 

기본 템플릿은 전체 사이트의 디자인과 느낌을 바꾸기 위해서 수정가능합니다. 기본 템플릿에는 디스플레이, 수정, 그리고 삽입을 위한 템플릿이 포함되어 있습니다. 새로 추가하는 템플릿들은 데이터객체의 이름을 기반으로 해서 추가될 수도 있는데, 그 타입의 객체의 디자인과 느낌을 수정하고 싶을때가 그렇습니다. 예를들면, 아래와 같은 템플릿을 추가할 수 있습니다.

 

\DynamicData\EntityTemplates\Products.aspx

이 템플릿은 아래와 같은 마크업을 포함하고 있겠죠.

 

<tr>

  <td>Name</td>

  <td><asp:DynamicControl runat="server" DataField="ProductName" /></td>

  <td>Category</td>

  <td><asp:DynamicControl runat="server" DataField="Category" /></td>

</tr>

 

새로운 Entity templates DynamicEntity 컨트롤을 사용하는 페이지에서 화면에 나타납니다. 런타임에 이 컨트롤은 Entity template의 내용으로 대체되는데요. 아래의 마크업은 Entity template를 사용하는 Detail.aspx 페이지 템플릿의 FormView컨트롤입니다. 마크업의 DynamicEntity 요소을 주목해서 보시죠.

 

<asp:FormView runat="server" ID="FormView1"

    DataSourceID="DetailsDataSource"

    OnItemDeleted="FormView1_ItemDeleted">

  <ItemTemplate>

    <table class="DDDetailsTable" cellpadding="6">

      <asp:DynamicEntity runat="server" />

      <tr class="td">

        <td colspan="2">

          <asp:DynamicHyperLink ID="EditHyperLink" runat="server"

              Action="Edit" Text="Edit" />

          <asp:LinkButton ID="DeleteLinkButton" runat="server"

              CommandName="Delete"

              CausesValidation="false"

              OnClientClick='return confirm("Are you sure you want to delete this item?");'

              Text="Delete" />

        </td>

      </tr>

    </table>

  </ItemTemplate>

</asp:FormView>

 

 

- New Field Templates for URLs and E-mail Addresses

 

ASP.NET 4 에서는 새로운 내장 필드 템플릿인 EmailAddress.ascx, Url.ascx가 추가되었습니다. 이 템플릿들은 DataType 속성과 함께 EmailAddress Url로 선언된 필드에 사용됩니다. EmailAddress타입의 객체의 경우에는 필드가 ‘mailto:protocol’를 사용해서 만든 하이퍼링크로 표시되구요, 사용자가 이걸 클릭하면 사용자의 이메일 클라이언트 프로그램을 실행하고 기본메세지를 생성해줍니다. Url타입의 객체는 그냥 일반적인 하이퍼링크로 표시되구요.

 

아래의 예제는 필드를 마크하는 방법을 보여줍니다.

 

[DataType(DataType.EmailAddress)]

public object HomeEmail { get; set; }

 

[DataType(DataType.Url)]

public object Website { get; set; }

  

- Creating Links with the DynamicHyperLink Control

 

Dynamic Data는 사용자가 웹사이트가 접근했을 때 보여지는 URL을 컨트롤하기 위해서 .NET 프레임워크 3.5 SP1에 추가되었던 라우팅 기능을 사용합니다. DynamicHyperLink 컨트롤이 Dynamic Data를 사용한 사이트에 접근하는 링크를 쉽게 만들 수 있도록 도와줍니다. 아래의 예제는 DynamicHyperLink컨트롤을 사용하는 예제입니다.

 

<asp:DynamicHyperLink ID="ListHyperLink" runat="server"

    Action="List" TableName="Products">

  Show all products

</asp:DynamicHyperLink>

 

이 마크업은 Global.asax파일에 정의된 규칙대로 라우팅되는 링크를 만들고, 그 링크는 Products테이블의 List페이지를 가리킵니다. DynamicHyperLink컨트롤은 컨트롤이 위치한 Dynamic Data 페이지에 기반해서 자동으로 테이블의 이름을 명시해줍니다.

 

 

- Support for Inheritance in the Data Model

 

현재 Entity Framework LINQ to SQL 둘다 모두 데이터 모델상에서의 상속을 지원합니다. 예를 들면, InsurancePolicy 테이블이 있는 데이터베이스를 생각해볼 수 있는데요. 추가로 InsurancePolicy와 동일한 필드를 가지고 몇가지 추가적인 필드만 가지는 CarPolicy HousePolicy테이블도 가질 수 있습니다. Dynamic Data는 이렇게 데이터 모델에서 상속관계를 갖는 객체에 대해서 scaffolding을 할 수 있도록 개선되었습니다.

 

 

- Support for Many-to-Many Relationships(Entity Framework Only)

 

엔티티 프레임워크는 다대다 관계를 가지는 테이블을 풍부한 기능으로 지원하고 있고, 관계를 엔티티 객체들간의 컬렉션으로 노출하는 형태로 구현이 되어있습니다. ManyToMany.ascx ManyToMany_Edit.ascx 필드 템플릿은 다대다 관계를 갖는 데이터를 표시하고 수정하는 걸 지원하기 위해서 추가되었습니다.

 

 

- New Attributes to Control Display and Support Enumerations

 

DisplayAttribute를 통해서 필드가 어떻게 화면에 표시될지를 추가적으로 컨트롤 할 수 있습니다.기존 Dynamic Data버전에 있던 DisplayName 속성은 필드의 캡션으로 쓰일 이름을 변경할 수 있도록 지원했었는데요. 새로운 DisplayAttribute 클래스는 필드를 표시하는데 있어서 필드의 데이터를 어떻게 정렬할 것인지, 필드에 필터를 적용할 것인지 같은 옵션을 제공합니다. 그리고 이 속성은 추가적으로 GridView컨트롤의 레이블, DetailsView컨트롤의 이름, 필드의 도움말, 필드의 워터마크(필드가 텍스트입력을 받는다면)등에 대한 독립적인 제어를 제공합니다.

 

EnumDataTypeAttribute 클래스는 필드를 열거형데이터 타입에 연결해주기 위해서 추가되었습니다. 이 속성을 필드에 주게되면, 열거형 타입을 명시하게 되는데요. Dynamic Data에서는 열거형 값을 표시하고 수정하는데 Enumeration.ascx를 사용합니다. 이 템플릿은 데이터의 값을 열거형의 이름과 연결합니다.

 

 

- Enhanced Support for Filters

 

Dynamic Data 1.0boolean열과 외래키열에 대한 내장필터를 갖고 릴리즈 되었었습니다. 하지만,이 필터들은 열을 화면에 표시할지, 열의 데이터를 어떤 순서로 출력할지 명시해줄 수는 없었는데요. 새로운 DisplayAttribute 속성은 이 문제를 화면에 열을 필터로 표시할지 그리고 어떤 순서로 표시할지 설정할 수 있게 함으로써 해결해줍니다.

추가적으로 향상된 부분은 필터링을 지원하는 부분이 웹폼의 QueryExtender를 이용하도록 재작성됐다는 점입니다. 이로써 필터가 사용될 데이터 소스 컨트롤을 몰라도 필터를 작성할 수 있습니다. 이런 확장과 더불어서 이젠 필터도 템플릿 컨트롤로 작성되게 되어서 새로운 필터를 작성해서 추가할 수 있게 되었습니다. 마지막으로, DisplayAttribute 클래스는 UIHint가 기본적인 필드템플릿을 오버라이드할 수 있게 지원하는 것 처럼, 기본적인 필터를 오버라이드할 수 있도록 지원합니다.



- 마치면서

다음번에는 웹 디자이너와 배포에 어떤 새로운 기능들이 추가되고 개선되었는지에 대해서 말씀드리겠습니다!

Welcome to Dynamic C#(7) - 아낌없이 표현해 주는 나무

C# 2009. 9. 12. 08:30 Posted by 알 수 없는 사용자

- 럭키 세븐 -_-v

기분도 좋게 일곱번째 글이 되는 오늘은 아낌없이 표현해주는 나무, expresion tree를 가지고 이야기 해보도록 하겠습니다. LINQ의 뒤를 든든하게 받치고 있는 요소지만, 전면에 거의 드러나지 않아서 이게 뭔지 알아보려고 노력안하면 볼일이 없는 친구입니다. 저 역시 LINQ로 프로젝트를 진행하면서도 얉은 지식과 호기심으로 인해서 이런게 있다는 것도 모르고 있었는데요. 그리고 6월에 있었던 세미나에서는 '컴파일러같은 툴 개발자들에게나 적합한 기능인거 같다'는 망언을 하기도 했었습니다. 뭐 100% 틀린 말은 아니겠지만, 이해가 부족한 탓에 나온 망언이었던거니 혹시 마음상한 분 있으셨다면 여친한테 밴드라도 붙여달라고 하시면 좋을거 같네요. 여친없으시면 어머니한테라도...


- 표현해주는 나무나 빨랑 설명해봐

일단 expression tree는 실행가능한 코드를 데이터로 변환가능한 방법을 제공해주는 요소입니다. 이렇게 데이터로 변환하게 되면 컴파일하기 전에 코드를 변경한다거나 하는 일이 매우 수월해지는데요. 예를 들면, C#의 LINQ 쿼리식을 sql 데이터베이스같이 다른 프로세스상에서 수행하는 코드로 변환하는 경우 말이죠.

Func<int, int, int> function = (a, b) => a + b;

위와 같은 문장은 머리,가슴,배는 아니지만 세부분으로 구성됩니다.

1. 선언부 : Func<int, int, int> function
2. 등호 연산자 : =
3. 람다 식 : (a, b) => a + b;

현재 변수 function은 두 숫자를 받아서 어떻게 더하는지를 나타내는 코드를 참조하고 있습니다. 그리고 위의 람다 표현식을 메서드로 표현해본다면 대략 아래와 같은 모양이 되겠죠.

public int function(int a, int b)
{
    return a + b;
}

Func는 System네임스페이스에 아래와 같이 선언되어 있습니다.

public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2); 

이 선언이 우리가 두개의 숫자를 더하는 간단한 람다식을 선언하는 걸 도와준 셈이죠.


- 근데, 어케 코드에서 표현해주는 나무로...?

위에서 우리는 실행가능한 코드를 function이라는 변수를 통해서 참조할 수 있다는걸 봤습니다. 근데, expression tree는 실행가능한 코드나 아니고, 자료구조의 한 형태입니다. 그러면, 어떻게 표현식을(코드를) expression tree로 변환하는 걸까요? 그래서 LINQ가 좋은걸 준비해뒀습니다.

using System.Linq.Expressions;

....

Expression<Func<int, int, int>> expression = (a, b) => a + b; 

위와 같이만 하면, 람다식이 expression tree로 쑉~! 하고 변환이 됩니다. 그럼 이걸 좀 더 눈에 보이게 살펴볼까요? 여기에 가시면, C#으로 구현된 예제들을 받을 수 있는데요, 예제중에 ExpressionTreeVisualizer(이하, ETV)라는게 있습니다. expression tree를 TreeView컨트롤을 이용해서 보기쉽게 쪼개주는 놈이죠. (a, b) => a + b;를 한번 확인해볼까요?



위의 간단한 예제코드는, Expression<TDelegate>클래스를 사용하고 있는데요, 그 클래스의 4가지 프로퍼티를 ETV를 통해서 확인해보실 수 있습니다. 좀 더 확실하게 하기 위해서 '-'를 눌러서 다 접어볼까요?



위 그림을 보시면, 딱 4가지만 남아있죠?

  • Body : expression의 몸체를 리턴한다.
  • Parameters : 람다식의 파라미터를 리턴한다.
  • NodeType : expression tree의 특정노드의 ExpressionType을 리턴한다. ExpressionType은 45가지의 값을 가진 열거형타입인데, expression tree에 속할 수 있는 모든 노드의 목록이 포함되어 있다. 예를 들면, 상수를 리턴하거나, 파라미터를 리턴한다거나, 둘 중에 뭐가 더 큰지 결정한다거나(<,>), 두 값을 더한다거나(+) 하는 것들이 있다.
  • Type : expression의 정적인 타입을 리턴한다. 위의 예제같은 경우에는 Func<int, int, int>가 되겠다.

아래와 같은 코드를 통해서 위의 프로퍼티들의 값을 확인해볼 수 있습니다.

using System;
using System.Linq.Expressions;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            Expression<Func<int, int, int>> expr = (a, b) => a + b;

            BinaryExpression body = (BinaryExpression)expr.Body;
            ParameterExpression left = (ParameterExpression)body.Left;
            ParameterExpression right = (ParameterExpression)body.Right;

            Console.WriteLine(expr.Body);
            Console.WriteLine("표현식의 왼쪽 : {0}\n노드의 타입 : {1}"
                + "\n표현식의 오른쪽 : {2}\n몸체의 타입 : {3}",
                left.Name, body.NodeType, right.Name, body.Type);
        }
    }
}


그리고 실행결과는 아래와 같습니다.



결과에서 expression의 모든 요소들이 각각의 노드로 이루어진 자료구조라는 것을 확인해보실 수 있습니다. 그리고 반대로 expression tree를 코드로 변환해서 실행하는 것도 매우 간단합니다. 아래와 같이 딴 한줄이면 됩니다.

int result = expr.Compile() (3,5); 


- 근데 당췌 왜 코드를 나무로 바꾸는건데? 식목일이냥?

이제 expression tree에 대해서는 조금 익숙해지셨을 겁니다. 특히 이 expression tree가 LINQ to SQL에 아주 중요한 역할을 하는데요, 일단 아래의 LINQ to SQL 쿼리문을 보시져~.

var query = from u in db.Users
     where u.nickname == "boram"
     select new { u.uId, u.nickname };


위 쿼리문의 결과로 반환되는 타입을 확인해보면 아래와 같습니다.



넵 바로 IQueryable인데요, IQueryable의 정의를 확인해보면 아래와 같습니다.

public interface IQueryable : IEnumerable
{
    Type ElementType { get; }
    Expression Expression { get; }
    IQueryProvider Provider { get; }

즉, 멤버로 Expression타입의 프로퍼티를 가지고 있습니다. IQueryable의 인스턴스는 expression tree를 가지고 있도록 설계된 거죠. 그 expressio tree가 바로 코드로 작성한 LINQ쿼리문의 자료구조입니다. 그런데, 왜 이렇게 LINQ to SQL쿼리를 expression tree형태로 가지고 있는걸까요? 그 핵심은, A라는 프로그램의 코드에 LINQ to SQL쿼리가 있다고 했을때, 실제로 이 쿼리가 수행되는 곳이 A가 아니라 데이터베이스 서버라는 점에 있습니다. 즉, 프로그램에서 직접실행되는게 아니라 데이터베이스가 알아들을 수 있는 SQL쿼리형태로 변환을 해서, 그 쿼리를 데이터베이스에게 날려서 쿼리된 데이터를 받아온다는 거죠. 위의 LINQ to SQL쿼리는 대략 아래와 같은 SQL문으로 변환이 됩니다.

SELECT [t0].[uId], [t0].[nickname]
FROM [dbo].[Users] AS [t0]
WHERE [t0].[nickname] = @p0 

프로그램내에 존재하는 쿼리표현식은 SQL 쿼리로 변환되어서 다른 프로세스에서 사용되게끔 문자열 형태로 보낸다는 거죠. 그러면, 위에서 설명드린대로 실제 쿼리는 데이터베이스의 프로세스내에서 처리가 되구요. 즉, IL코드를 SQL쿼리로 변환하는 거 보다, expression tree같은 자료구조형태가 변환하기 훨씬 쉬울뿐더러, 최적화 같은 중간처리도 훨씬 용이하다는 겁니다. 하지만 LINQ to Objects를 통해서 쿼리를 해보면 결과의 타입은 IEnumerable<T>입니다. 왜 얘네들은 IQueryable<T>가 아닐까요? IEnumerable<T>의 정의를 보면 아래와 같습니다.

public interface IEnumerable<T> : IEnumerable
{
    IEnumerator<T> GetEnumerator();

즉, Expression타입의 프로퍼티가 없습니다. 왜냐면, LINQ eo Objects는 같은 프로세스내에서 처리될 객체들을 대상으로 쿼리를 하기 때문에 다른 형태로 변환될 필요가 없기 때문입니다. 그렇다면, 굳이 expression tree같은 자료구조로 변환할 필요가 없겠죠. 그래서 대략 아래와 같은 규칙이 성립합니다.
  • 코드가 같은 프로그램(또는 프로세스)내에서 실행되는 경우라면 IEnumerable<T>
  • 쿼리 표현식을 다른 프로그램(또는 프로세스)에서 처리하기 위해서 문자열 형태로 변환해야 한다면 expression tree를 포함하는 IQueryable<T>를 사용 


- 마치면서

일단 오늘은 C# 3.0에 포함되었던 expression tree에 대해서 설명을 드렸습니다. DLR이랑 dynamic이야기 하다가 난데없이 삼천포로 빠진 느낌이 드시겠지만(저는 경남 진주에 살았었는데, 아버지 따라 삼천포 자주 갔었습니다....이 이야기는 왜하는 거지..-_-), DLR에서 expression tree가 중요하게 사용되고, 또한 C# 3.0을 사용해보신 분들이라도 expression tree에 대해서 제대로 못짚고 넘어간 분들도 많으리라 생각합니다. 저 역시 그랬구요. 아무튼, 도움되셨기를 바라면서 다음에 뵙죠!


- 참고자료

1. http://blogs.msdn.com/charlie/archive/2008/01/31/expression-tree-basics.aspx

- 이건 또 뭥미

이 포스트 시리즈는 http://www.asp.net/learn/whitepapers/aspnet40/ 에 올라와있는 "ASP.NET 4.0 and Visual Studio 2010 Web Development Beta 2 Overview"를 번역하는 시리즈입니다. 번역이라서 조금은 딱딱할 수도 있고, 내공의 부족으로 제대로 번역이 힘든 부분도 있습니다.(반드시 있습니다-_-) 따쓰한 피드백으로 격려를 부탁드립니다. 냐하하하하. 두부분으로 나눠서 번역을 맡았는데요, 제가 맡은 부분은 Dynamic Data, Studio 2010 Web Designer Improvement, Web Application Deployment with Visual Studio 2010 이렇게 세부분입니다. 그럼 시작해볼까효?


- Dynamic Data

 

Dynamic Data 2008년 중반에 릴리즈되었던 .NET 프레임워크 3.5 서비스팩 1에서 처음 소개가 됐습니다. 이 기술은 데이터 중심 개발을 하는데 있어서 많은 유용한 기능을 제공하는데요. 예를 들면 아래와 같습니다.

 

l  데이터 중심의 웹사이트를 RAD(빠른 속도의 어플리케이션 개발)처럼 작성

l  데이터 모델에 명세된 제약조건을 기반으로 자동으로 유효성검사

l  Dynamic Data 프로젝트의 일부인 필드 템플릿을 이용해서 GridView DetailsView의 자동생성된 필드의 마크업을 쉽게 변경할 수 있음

 

더 상세한 정보는 MSDN에 있는 Dynamic Data documentation을 참조하시면 됩니다.

 

ASP.NET 4 에선 기존의 기능보다 더 강력해진 지원으로 데이터중심의 웹사이트를 더 빠르게 개발할 수 있도록 도와줍니다.

 

- Enabling Dynamic Data for Existing Projects

 

.NET 프레임워크 3.5 서비스팩 1 에 추가되었던 Dynamic Data는 아래와 같은 새로운 기능들을 소개했었죠.

 

l  필드 템플릿 데이터 바운드 가능한 컨트롤에 대해서 데이터 타입에 기반한 템플릿을 제공한다. 필드 템플릿은 기존에 각각에 필드에 템플릿 필드를 이용해서 설정해줘야 하던것에 비해서 컨트롤의 모양을 입맛에 맞게 커스터마이즈하는 좀 더 간편한 방법을 제공한다.

l  유효성검사 데이터 클래스에 어트리뷰트를 이용해서 값의 유무, 범위 체크, 타입 체크, 정규식을 이용한 패턴매칭, 사용자 정이 유효성검사와 같은 일반적인 경우에 대한 유효성검사를 명시해줄 수 있다.

 

하지만, 이런 기능들은 아래와 같은 요구사항이 있었습니다.

 

l  데이터 엑세스 레이어는 반드시 Entity Framework LINQ to SQL이어야 한다.

l  데이터 소스 컨트롤은 EntityDataSource LinqDataSouce컨트롤 이 두가지 외에는 지원되지 않는다.

l  위와 같은 기능들을 모두 사용하기 위해서는 필요한 파일들이 모두 포함된 템플릿인 Dynamic Data Dynamic Data Entities를 통해 생성된 웹 프로젝트여야만 했다.

 

ASP.NET 4에 추가될 Dynamic Data에서 가장 초점을 둔 부분중의 하나는 어떤 ASP.NET 어플리케이션이라고 해도 Dynamic Data의 기능을 활용할 수 있도록 한다는 것 입니다. 아래의 예제코드는 이런 목표에 맞게, 기존에 존재하던 페이지에 Dynamic Data의 기능을 활용하기 위해서 명시해줘야 하는 코드입니다.

 

<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="True"

    DataKeyNames="ProductID" DataSourceID="LinqDataSource1">

</asp:GridView>

<asp:LinqDataSource ID="LinqDataSource1" runat="server"

    ContextTypeName="DataClassesDataContext" EnableDelete="True" EnableInsert="True"

    EnableUpdate="True" TableName="Products">

</asp:LinqDataSource>

 

페이지의 코드비하인드에서 이 컨트롤들의 Dynamic Data기능을 켜려면 아래와 같은 코드를 반드시 추가해야 합니다.

 

GridView1.EnableDynamicData(typeof(Product));

 

GridView 컨트롤이 수정모드에 있을 때, 사용자가 입력한 값이 적절한 포맷인지 아닌지를 Dynamic Data가 자동으로 검사를 하고, 포맷에 맞지 않는다면 에러메세지를 보여줍니다.

 

이 기능은 입력모드에서 사용될 기본값을 명시해줄 수 있는 것 같이 다른 장점도 가지는데요. Dynamic Data를 쓰지 않고 필드의 기본값을 설정하려면, 이벤트를 걸어야 되고, FindControl등의 메서드를 이용해서 컨트롤을 찾아야 되고, 값을 설정해야 하는등의 작업을 해야합니다. 하지만 ASP.NET 4에서는, EnableDynamicData메서드에서 아래의 예제코드에서 처럼 어떤 필드에도 기본값을 설정해줄 수 있습니다.

 

DetailsView1.EnableDynamicData(typeof(Product), new { ProductName = "DefaultName" });

 

 

- Declarative DynamicDataManager Control Syntax

 

다른 ASP.NET컨트롤들이 그렇듯이 DynamicDataManager컨트롤도 이제는 코드에서 뿐만 아니라 선언적으로 속성을 명시해줄 수 있게 개선되었습니다. 아래 예제에서 DynamicDataManager 컨트롤의 마크업을 확인할 수 있습니다.

 

<asp:DynamicDataManager ID="DynamicDataManager1" runat="server"

    AutoLoadForeignKeys="true">

  <DataControls>

    <asp:DataControlReference ControlID="GridView1" />

  </DataControls>

</asp:DynamicDataManager>

 

<asp:GridView id="GridView1" runat="server"

</asp:GridView>

 

이 마크업은 DataControls섹션에 나와있듯이 GridView1 Dynamic Data기능을 적용시킵니다.



- 마치면서

쫌 이상해 보이긴 하네요-_-ㅋ 다음 포스트에서는 Entity Templates부터 시작해서 템플릿에 대한 내용을 설명드리도록 하겠습니다.

[JumpToDX11-4] ID3D11View

DirectX 11 2009. 9. 7. 18:00 Posted by 알 수 없는 사용자

 

이번 회에서는 일단 소스를 좀 나열해 보겠습니다.
일단 변수들은 다음과 같습니다.




이어지는 상황은 다음과 같습니다


 

BackBuffer 를 설정했습니다. 일단 'View' 라는 키워드에 주목해 주시기 바랍니다.
그리고 다음에 이어지는 상황을 다시 보겠습니다. 

 

 




이어졌던 소스는 Depth-Stencil Buffer 생성을 위한 작업이였습니다.

역시나 낯선 개념이 등장하는데, 바로 'View' 입니다.
우리가 앞서 생성했던 BackBuffer 와 Depth-Stencil Buffer 에는 CreatexxxxxxxView() 형태로
작업을 해주고 있습니다.

하나의 뷰는 렌더링 작업 중에 파이프라인에서 접근할 수 있는 리소스 일부분을 의미합니다.

더 정확한 개념을 위해서 아래의 계층구조를 한번 살펴보시기 바랍니다.






우리가 사용하는 모든 리소스들은 사실 ID3D11Resource 를 상속받아서 구성됩니다.

텍스쳐나 각종 버퍼 등이 이에 속한다고 할 수 있습니다.

( ID3D11Buffer, ID3D11Texture )

 

ID3D11View 를 상속받아서 구현되는 것들은 ID3D11RenderTargetView, ID3D11DepthStencilView, ID3D11ShaderResourceView 가 있습니다.

 

나열하고 보니깐 약간 감이 오시지 않으십니까?

이렇게 분리가 되었는데, 사실 ID3D11View ID3D11View::GetResource() 라는 멤버함수를 가지고 있습니다.

 

결국 ID3D11View ID3D11Resource 와 개념적으로 메모리 데이터는 동일하다고 볼 수 있습니다.

그러다 보니 이 두가지 인터페이스를 구별해서 설명하기가 무척 난해합니다.
비슷하면서도 실질적으로 수행되는 역활에는 엄연한 구분이 있으니 말이죠...

 

이렇게 분리함으로써 역할 구분이 명확해서 코드의 품질이 향상되었다라고 말할 수 있습니다.
조금 더 좋은 방향으로 생각해 본다면,
아마도 내부적으로 조금 더 최적화 된 위치에 메모리를 할당해 주지 않을까? 라는 의심도 해봅니다.
( 왜 그런지 이유를 꽤 오래 생각했지만, 답을 찾지 못했습니다..T.T )

 

 

참고로 얘기드리면,

DirectX9 버전까지 유용하게 사용되던 메모리 풀의 개념이 이제는 CPU 에서 읽기/쓰기 작업,
GPU
에서 읽기/쓰기 작업이 가능여부를 설정하는 개념형태로 변경
되었습니다.

이것에 대한 얘기는 다음 번에 저나 다른 분이 해주실 겁니다.

( DirectX11 파트에는 저 말고도 한분이 더 계십니다…^^ )

 

위의 코드에서 View 계열들은 GPU 만 읽기/쓰기 작업이 가능한 형태로 그래픽 카드상에
메모리를 할당했습니다.


결국 위의 작업은 바로 아래에 나오는 작업을 위해서 필요했던 것입니다.



보시듯이 모두 View 계열의 인터페이스를 설정했습니다.



< 마치면서... >
다음주는 한주 쉬게 될 것입니다.
스터디 내부 발표가 있어서...쿨럭~



 

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

C# 2009. 9. 3. 13:36 Posted by 알 수 없는 사용자

- 복습.

지난시간에서 이야기 했던 부분을 조금 이어서 이야기하자면요, 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

[JumpToDX11-3] Feature Level

DirectX 11 2009. 8. 31. 06:00 Posted by 알 수 없는 사용자



이번에는 실제로 API 를 사용해서 Device와 DeviceContext 를 설정하는 작업을 할 것입니다.
아마 다음과 같이 사용할 수 있을 것입니다.





D3D11CreateDeviceAndSwapChain 은 정말이지 마법 같은 API 입니다.
( 더 언급할 것도 없을 것 같습니다...^^ )


< Feature Level >

이전버전과의 호환을 유지하는 것이 중요한 일이기 때문에,
최신의 버전은 이전 버전들에 대한 호환을 생각하게 됩니다.
그래서 등장한 개념이 바로 'Feature Level' 입니다.
위의 코드에도 이 키워드가 등장했습니다.

약간씩의 차이가 있겠지만,
DirectX9 시절까지 우리가 Device 생성을 위해서 API 에 전달했던 인자들은
SDK 버전과 하드웨어 가속( HAL Device ) 의 사용여부 등의 인자였습니다.
( 이외에도 더 많이 있었지만, 지금은 이 두 가지가 중요해서 이것만 언급했습니다. )
그런 식으로 Device 생성 작업을 하면, 우리는 가속되는 하드웨어로 작업을 할 수 있었습니다.
( 저도 이 글 적기 전까지 별 다르게 생각하지 않았습니다...-_- )

그런데 이제는 API 를 통해서 전달해야하는 정보들 중에 Feature Level 이라는 것을 넘겨주어야 합니다.
각 DirectX 버전마다 각각의 특징을 설명하는 Feature Level 이 존재합니다.
우리가 할 일은 원하는 Feature Level 을 설정하고 Device 생성을 요구하는 것입니다.

Feature Level 은 사실 하드웨어적인 역활과 연관성이 깊습니다. 
우리가 사용하는 그래픽 카드는 제작될때 특정한 DirectX 버전에 포커스를 두고 출시하게 됩니다.
시중에 그래픽 카드를 구입할 때 
'DirectX11 을 완벽히 지원하는 그래픽 카드" 뭐 이런 식의 광고 많이 보셨으리라 생각이 됩니다.

그렇다는 것은 그래픽 카드에도 역시 버전이 있다는 것입니다.
Feature Level 이라는 것이 바로 그런 하드웨어의 버전 역활을 하는 것입니다.

 
현재 DirectX11 을 지원하는 그래픽 카드는 미출시 상태입니다.
그렇기 때문에 사실 DirectX11 프로그램은 하드웨어 가속을 시킬 수가 없습니다. ( 새로 사야하나...-_- )
DirectX9 시절에는 CreateDevice 옵션에 HAL 옵션 주면 자동(?)으로 가속되는 Device 를 생성해 주었지만,
이제는 그래픽 카드에서 구현된 버전을 정확히 얻어서,
그 Feature Level 에 맞는 Device 를 생성
시켜야 합니다.

예를 들면 제가 가진 컴퓨터가 DirectX 10 버전을 지원하는 그래픽 카드인데,
Feature Level 에 DirectX 10.1 버전과 하드웨어 가속 옵션을 주어서
Device 생성을 요구하면, 그것은 실패하게 됩니다.
그렇다는 것은 Feature Level 10 를 설정해 주어서 Device 를 생성 시켜주어야 한다는 의미입니다.

 이렇게 하면 우리는 DirectX 11 API 를 이용해서 낮은 버전의 DirectX 버전을 사용하는
그래픽 카드를 제어할 수 있을 것입니다.
아래의 그림이 이해에 도움이 되었으면 좋겠습니다.



그렇다면 좀 더 정확하게 우리가 할 일을 확장해 보면,
가장 최근의 Feature Level 부터 나열해보면서,
사용하는 그래픽 카드의 Feature Level 을 알아내는 것이 될 것입니다.
( 저는 위의 예제에서는 11버전만 고려해서 작업한 것입니다.^^ )

사실 예전에는 Device Caps 라는 개념이 있었는데,
사실 이것이 기능별로 체크했기 때문에 굉장히 반복적인 작업이였을 것이라 생각이 듭니다.
그것을 Feature Level 이라는 개념으로 단순화 시켰다고 보면 괜찮을 것 같습니다.

< 추가된 내용 >

각 Feature Level 에 대응되는 쉐이더 모델은 다음과 같습니다.

Feature Level 11.0 = Shade 5.0
Feature Level 10.1 = Shader 4.1
Feature Level 10.0 = Shader 4.0
Feature Level  9.3 = Shader 3.0
Feature Level  9.2 = Shader 2.0
Feature Level  9.1 = Shader 2.0







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

C# 2009. 8. 26. 15:02 Posted by 알 수 없는 사용자

- 그때 너 너무 대충하더라

제가 맨 처음 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/

[JumpToDX11-2]DeviceContext...넌 누구냣!!

DirectX 11 2009. 8. 24. 14:00 Posted by 알 수 없는 사용자



지난 회에서 DXGI 에 대해서 잠깐 살펴보았습니다.
DXGI 에 대한 사용방법은 여기서 언급하지 않습니다.
왜냐하면 제가 진행할 코딩에서는 기본적으로 셋팅되어 있는 그래픽 카드를 사용하는 것을 전제로
진행할 것이기 때문입니다.

혹시 호기심이 더 왕성하신 분들은 IDXGIFactory 인터페이스를 살펴보시면 됩니다.
멤버 함수중에 MakeWindowAssociation, EnumAdapters 정도 살펴보시면 도움이 될 것입니다.
( API 함수 이름만 봐도 느낌이 팍팍! )

예상하시겠지만, DXGI는 연결가능한 장치(어댑터)들을 나열해서
그것 중에 하나 선택하는 역활이 필요합니다.
저는 기본적으로 설정된 어댑터를 사용할 것이기 때문에 이들에 대해서는 언급하지 않겠습니다.
 DXGI 계열의 API들도 양이 상당합니다.( 그래서 일단 패스~ )


지난 회에 언급했던 디바이스 초기화를 위한 변수들 기억하시나요?
다시 나열해 보면 아래와 같습니다.




< IDXGISwapChain >

가장 먼저  IDXGISwapChain 에 대해서 살펴봐야하겠지만, 이것에 대한 별도의 설명이 필요할까요?
Front Buffer( 현재 화면에 보여지는 버퍼 )와 Back Buffer( 현재 연산을 해서 기록하는 버퍼 ) 를
준비해서 이것을 Flip 시키면서 번갈아 가면서 보여주는 것을 의미합니다.
( 너무 고전적인 내용이라 더 설명하면 혼날듯...)

우리가 렌더링할 영역(버퍼)에 대한 포맷과 같은 각종 속성들을 설정해 주어서 생성을 요구하고,
포인터를 받으면, 이들 버퍼에 대한 정보를 제어
할 수 있습니다.
나중에 살펴보게 되겠지만, Present() 라는 API 를 기억하시나요?
9.0 에서는 이것이 ID3DDevice9의 멤버함수로써 사용했었습니다.
하지만 현재는 IDXGISwapChain 의 멤버함수로 등록되어 잇습니다.
그래서 이에 대한 포인터가 필요합니다.^^
결론적으로 얘기 드리면 앞으로 화면 출력에 관한 모든 것은 IDXGI 계열의 인터페이스로서 제어할 수 있습니다.





아마 위와 같은 형식이겠죠? ( 각각의 성분에 대한 설명은 생략합니다...저걸 다 어찌 설명해요..-_- )

 

 

 < DeviceContext...넌 누구냣!! >

이상한 인터페이스가 DirectX11 에서 생겼습니다.
Device는 무엇인지 알겠는데, DeviceContext 는 또 무엇일까요?
사실 이것은 그동안 Device 인터페이스들이 해오던 역활을 두가지로 분리한 것에 지나지 않습니다.

즉, ID3D11Deivce 는 주로 리소스( 버퍼나 텍스쳐 등 )의 생성에 대한 인터페이스이며,
ID3D11DeviceContext 는  이들 리소스를 제어하고 관리하기 위한 인터페이스입니다.

그렇다면 왜 이렇게 두 가지로 분리된 것일까요?
먼저 아래의 그림을 살펴보겠습니다.




우리가 렌더링을 수행하기 위해서는 애플리케이션에서는 관련 Core API 와 Runtime을 사용하게 됩니다.
이들 Core API 와 Runtime 은 우리가 필요한 렌더링에 관한 모든 것을 수행합니다.
메모리 할당이나 리소스들의 수정, 메모리 바인딩, 각종 렌더링 스테이트의 제어 등등 굉장히 많죠.
( 물론 쉐이더 코드들을 통해서도 이들을 제어할 수 있는 부분이 있습니다만,
  여기서는 흐름상 고려하지는 않습니다.  ) 

DirectX 시스템은 Application 과의 오버헤드를 최소화 하기 위해서
이들 사이를 매우 얇은 추상화 단계로 디자인 했었습니다.
즉, Core API 나 Runtime 들은 바로 Driver 에 접근할 수 있었습니다.

그래도 약간(?) 존재해 있는 Application 과 하드웨어간의 오버헤드를 줄이기 위해서
기존의 Device 의 역활을 Device 와 DeviceContext 로 분리
하게 된 것입니다. 

그렇다면 여기서 발생되는 오버헤드란 것은 어떤 것일까요?( 의문에 의문 연속입니다..-_- )
우리가 사용하는 각종 API 들은 Runtime 에 전달되어서 하드웨어가 인식할 수 있는
커맨드( Command ) 들로 변환
됩니다.

Runtime 은 이들 커맨드들을 담을 수 있는 메모리 공간을 가지고 있어서, 커맨드들을 저장하게 됩니다.
그러다가 이들 버퍼가 가득차거나, 혹은 렌더링 데이터의 업데이트가 필요한 경우에
이들을 하드웨어로 전송하게 되는 것입니다.
바로 이 커맨드들에 대해서 오버헤드가 발생하는 것입니다.
이 커맨드들이 오버헤드를 발생시키는 이유는 여러가지가 있었습니다.
하드웨어의 경우에는 프로세싱( processing ) 스타일이 매우 다양하기도 했고,
API 와 하드웨어 상에서 커맨드 전달이 잘못 전달되는 경우도 있었다고 합니다.
( 아무래도 하드웨어가 너무 다양해서가 주된 이유였던 듯 합니다. )

이들에 대한 오버헤드를 줄이는 방법을 고민하던 중에 나온 결과물 중에 하나가
바로 'DeviceContext' 라는 것입니다.
( 뒤에 언급할 기회가 있겠지만, 'State Object' 가 바로 이 오버헤드를 줄이기 위해 등장한 개념이기도 합니다. )

Device 의 경우에는 오버헤드를 줄이기 위해 등장한 개념이
리소스의 생성/해제를 담당하는 커맨드들과 그 리소스들을 제어하는 커맨드들로 분리하는 것입니다.

 

분리함으로써 어떤 성능 향상이 있었을까요?
리소스의 생성과 해제는 사실 멀티스레드 형태의 API 호출에도 별 문제가 없습니다.
어차피 명령어들이 큐 형태로 쌓이게 될테니까요.
반면에 렌더링 커맨드들은 멀티스레드 형식으로 구성되면 큰일 나겠죠?

결국 Device 는 Free threaded 형식으로 구성되었고,
DeviceContext 는 그렇지 않다는 것
입니다.
Free threaded 형식으로 구성되었다는 것은 스레드에 안정성을 유지하기 위한
별도의 lock/unlock 작업이 필요없다는 것입니다.
멀티스레드에 안정적이라는 얘기는 스레드 세이프하다는 것입니다.

(정확하게 확신은 아직 드릴 수 없지만, 멀티스레드 관련 렌더링과도 관련이 있는 부분이 여기이지 않을까요.)

사실 리소스의 생성과 해제가 성능에 많은 부분을 차지한다고 볼때,
이렇게 분리되어진 것을 환영해야 할 것입니다.

 

 < 다음 회에는... >

글이 좀 길어지는 것 같아서 일단 여기서 마무리 합니다.
다음 회에는 나머지 초기화 부분에 대해서 계속 언급하겠습니다.^^

 





 

'DirectX 11' 카테고리의 다른 글

[DX11_#2]D3D Buffer( 2 / 2 )  (0) 2009.10.13
[DX11_#1]D3D Buffer( 1 / 2 )  (0) 2009.09.22
[JumpToDX11-4] ID3D11View  (0) 2009.09.07
[JumpToDX11-3] Feature Level  (0) 2009.08.31
[JumpToDX11-1] 사라진 Direct3D 오브젝트를 찾아서...  (8) 2009.08.17

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

C# 2009. 8. 20. 20:25 Posted by 알 수 없는 사용자

- 또 쓸데없는 생각 하냐?

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


- 빨랑 비교한거 내놔.

비교대상은 한 클래스에 있는 메서드를 그냥 호출하는 것과 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에 대해서 좀 더 심도 깊게 파보려고 생각중입니다. 좋은글이 많은데 잠수타고 정신줄 놓느라고 못보고 있었더군요!! 암튼. 곧 돌아오겠슴돠. ㅋㅋㅋ

[JumpToDX11-1] 사라진 Direct3D 오브젝트를 찾아서...

DirectX 11 2009. 8. 17. 14:00 Posted by 알 수 없는 사용자

< 인사 및 소개 >

안녕하세요.
저는 이번에 vsts2010 에 참여하게 된 조진현 이라고 합니다.

어떤 주제에 대해서 글을 쓰다는 것은 무척 어려운 일입니다.

그렇기 때문에, 이 스터디 참가를 굉장히 망설이기도 했습니다.
많은 분들과 함께 열정을 가지고 참가를 결심했고, 드디어 처음으로 글을 남기게 되었습니다.
제가 가장 우려하는 것은 잘못된 지식을 전달하는 것입니다.
그래서 조심스러운 마음으로 글을 작성할 것입니다.
잘못된 부분이나 미흡한 부분이 있으면, 바로 지적해주시면 감사하겠습니다.

제가 언급할 큰 주제는 DirectX 11 과 관련이 있습니다.
그 중에서도 멀티 코어를 활용한 DirectX 사용에 초점을 두고 글을 전개할 생각입니다.
글의 주요 대상은 DirectX9 를 사용하시다가 DirectX11 을 사용하고자 하시는 분들입니다.

일단 방대한 변화에 대해서 모두 나열하기는 힘듭니다.

그래서 간단히 제가 코딩을 하면서 필요했던 API 위주로 살펴보면서 변화를 언급하고자 합니다.
그런데 하나 문제가 있습니다.
현재 DirectX 11 은 하드웨어 가속이 지원되지 않습니다.
오직 REF 모드로만 작동을 합니다.
아마도 아직 정식으로 widnows 7 이 출시가 이루어지지 않아서 그런 듯 합니다.
이점, 꼭 주의하시기 바랍니다.
괜히 DirectX 11 예제 실행했다가, 실행 성능이 떨어진다고 컴퓨터를 부수는 행위는 자제해 주세요.^^


< 사라진 Direct3D 오브젝트를 찾아서... >

우리가 가장 먼저 접하게 되는 DirectX 의 API 는 CreateDevice() 일 것입니다.
사실 이전 버전까지는 CreateDevice() 에 대해서 별도로 언급할 내용이 없었을 것이지만,
늘(?) 그렇듯이 DirectX 의 변화를 설명해주는 API 가 바로 CreateDevice() 입니다.
일단 CreateDevice() 를 위한 관련 변수들부터 봐야겠죠?
 



잠깐!!
가장 먼저 헤더 파일들을 살펴보는게 순서이죠.

헤더는 다음과 같이 변경되었습니다.
굳이 헤더의 용도에 대해서 일일이 나열하지는 않았습니다.

// Direct3D11 includes
#include <dxgi.h>
#include <d3d11.h>
#include <d3dCompiler.h>
#include <d3dx11.h>
#include <dxerr.h>

 

라이브러리 링크는 아래의 것들을 해주시면 됩니다.

#pragma comment( lib, "dxguid.lib" )
#pragma comment( lib, "d3dcompiler.lib" )
#pragma comment( lib, "dxerr.lib" )
#pragma comment( lib, "dxgi.lib" )
#pragma comment( lib, "d3d11.lib" )
#pragma comment( lib, "d3dx11.lib" )




변수들을 나열해 보겠습니다.




 생소한 부분이 눈에 보이시나요?
 'ID3D11DeviceContext' 라는 것이 새롭게 등장했습니다. ( 다음 번에 언급할 것입니다. )
 그리고 Direct3D 인터페이스가 사라진 것을 찾으셨습니까?




위의 그림은 DirectX 9 의 아키텍쳐입니다.
우리가 작성하는 프로그램은 오직 Direct3D 나 GDI 를 통해서 저수준의 하드웨어와 통신을 할 수 있었습니다.

그런데 현재의 DirectX 아키텍쳐는 아래와 같습니다.



 여기서 또 하나 생소한 것이 등장했습니다.
바로 DXGI ( DirectX Graphics Infrastructure ) 입니다.
"DirectX9 에서 사라진 'Direct3D 오브젝트'를 'DXGI' 가 대체하는게 아닐까?" 라는 의문이 들었다면,
박수를 보내드리고 싶습니다.( 브라보~~ )


네, 맞습니다.
'DXGI' 라는 것이 바로 사라진 'Direct3D 오브젝트' 입니다.
'Direct3D 오브젝트' 의 역활에 대해서 혹시 기억하십니까?
하드웨어와 연결된 디바이스들을 나열하고, 모니터로 출력되는 결과들을 관리해주기도 했었습니다.
우리가 관리하기 힘든 저 수준의 작업들을 바로 이 'Direct3D 오브젝트'가 했었습니다.
그런데 이제는 이것을 'DXGI' 가 해주고 있습니다.
( IDXGISwapChain 보이시나요? 이것도 다음 회에 언급하겠습니다. )
 

아키텍쳐 구조를 보시면 아시겠지만, DirectX9 까지는 일반 애플리케이션에서 DirectX API 를 통하지 않고는
DirectX 를 사용할 수 없었습니다.
그런데 최근에는 일반 애플리케이션은 모두 DXGI 를 통해서 DirectX 를 사용하고 있습니다.
( 저만 놀라운 것은 아니겠죠? +_+ )
마이크로소프트에서도 강조하고 있는 사실 중에 하나가 바로 DirectX 는 더 이상 게임만을 위한 것이 아니라는 것입니다.
이제 사라진 줄 알았던 'Direct3D 오브젝트' 가 DXGI 라는 사실을 알았습니다.
앞으로 저수준의 작업이 필요하면 DXGI 를 직접 제어하거나 DirectX API 를 이용하셔도 됩니다.


< 다음 회에는... >

다음 번에는 실제로 DirectX API 를 이용한 초기화 작업에 대해서 다루고자 합니다.
즉, 우리가 앞서 선언했던 변수들에 대한 이야기를 하겠습니다.

'DirectX 11' 카테고리의 다른 글

[DX11_#2]D3D Buffer( 2 / 2 )  (0) 2009.10.13
[DX11_#1]D3D Buffer( 1 / 2 )  (0) 2009.09.22
[JumpToDX11-4] ID3D11View  (0) 2009.09.07
[JumpToDX11-3] Feature Level  (0) 2009.08.31
[JumpToDX11-2]DeviceContext...넌 누구냣!!  (1) 2009.08.24

Welcome to F#(12) - 공동작업 좋치아니항가

F# 2009. 7. 16. 14:38 Posted by 알 수 없는 사용자

- F#의 첫 라이브러리 출연(Feat. C#)

오늘은 간단하게, F#에서 만든 코드를 C#에서 사용해보는 시간을 갖겠습니다. 자~ 간단하게 간단하게~.

우선, C#으로 콘솔프로그래밍 프로젝트를 하나 생성하고 같은 솔루션에 F# 라이브러리 프로젝트를 하나 추가합니다. 대략 아래와 같은 모양이 됩니다. 



그리고 Module1.fs에다가 아래의 코드를 작성합니다. 


 module FirstModule =
    type Type1 =
        { num: int }
        member self.reverse = { num = -self.num }
        member self.add(num2) = { num = self.num + num2 }
       
type Type2 =
    { num: int }
    member self.reverse = { num = -self.num }
    member self.add(num2) = { num = self.num + num2 }

let add5 num = num + 5



FirstModule라는 모듈을 선언하고 그 안에 Type1이라는 타입을 선언한 걸 보여주고 있습니다. 그리고 Type1의 멤버로는 int타입의 num이 있고 reverse, add라는 함수가 있습니다. 각각의 함수뒤의 {}안의 코드는 num에 새로운 값을 할당해서 Type1의 새로운 객체를 리턴하는 내용으로 보시면 이해가 되실 겁니다. 그리고 그 아래에는 모듈선언없이 그냥 Type2라는 타입을 선언했구요, 그 밑에는 함수 add5를 선언했습니다. 이런 코드들을 C#에서 참조한다면 어떻게 쓸 수 있을까요? 

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

namespace CoWorkWithFSharp
{
    class Program
    {
        static void Main(string[] args)
        {
            Module1.FirstModule.Type1 type1 = new Module1.FirstModule.Type1(5);
            Module1.FirstModule.Type1 type1_2 = type1.reverse;

            Console.WriteLine(type1_2.num);

            Module1.Type2 type2 = new Module1.Type2(6);
            Module1.Type2 type2_2 = type2.add(9);

            Console.WriteLine(type2_2.num);

            Console.WriteLine(Module1.add5(12));
        }
    }
}



위 코드를 보시면, Module1이라는 파일이름이 가장 먼저 오는 걸 볼 수 있습니다. 파일이름자체가 모듈이 되는거죠. 베타1이라서 그런지는 모르겠지만, F#으로 만든 코드는 "using Module1;" 형태로 using을 할 수 없었습니다. 에러메세지를 보면 네임스페이스가 아니라서 안된다고 하니 모듈도 클래스로 취급이 되는거 같은데 개선이 될지 아니면, 이대로 남을지 모르겠네요. 코드를 보시면 알겠지만, 매번 모듈명을 반복해서 적어줘야 하니 좀 빡세긴 합니다만;;;; 

아무튼, 저렇게 F#에서 작성한 타입의 객체를 생성할 수 있구요, 멤버 메서드와 함수를 호출할 수도 있습니다. 다만, 함수의 결과로 새로운 객체가 생성된다는 점 기억하시구요. 실행결과는 아래와 같습니다. 



그러면, 지난주에 만들었던 Discriminated Union을 이용한 간단한 사칙연산 코드를 C#에서 사용해보도록 하겠습니다. 아래코드를 Module1.fs에 추가하구요 

type Expr =
    | Num of float
    | Add of Expr * Expr
    | Sub of Expr * Expr
    | Mul of Expr * Expr
    | Div of Expr * Expr

let rec Calc expr =
    match expr with
    | Num num -> num
    | Add (e1, e2) -> (Calc e1) + (Calc e2)
    | Sub (e1, e2) -> (Calc e1) - (Calc e2)
    | Mul (e1, e2) -> (Calc e1) * (Calc e2)
    | Div (e1, e2) -> (Calc e1) / (Calc e2)



아래 코드를 C#코드에 추가해줍니다. 

 Module1.Expr expr = new Module1.Expr._Mul(
    Module1.Expr.Add(
        Module1.Expr.Num(5.0),
        Module1.Expr.Num(6.0)),
        Module1.Expr.Div(
            Module1.Expr.Num(5.0),
            Module1.Expr.Num(2.0)));

Console.WriteLine(Module1.Calc(expr));



위 코드를 보시면, 지난주에 계산을 위해서 만들었던 식을 그대로 C#에서 생성했습니다. 실행결과는 아래와 같습니다. 




-정리하며

아~ 간단한 포스트였군요~. 대략 F#에서 만든 코드를 이런식으로 C#등의 다른 언어에서 호출해서 사용할 수 있습니다. 어느덧 베타1이 나온지도 꽤 됀 느낌이네요. 많이들 익숙해지셨나요? VS2008에서 뭔가를 만들다가 C#4.0의 dynamic을 쓰면 딱이겠다고 생각하는 순간, 아쉬움이 느껴지더군요. 아직 F#은 그만큼 익숙하지 못해서 그런경우는 없었던거 같네요-_-;;; 언제쯤 F#의 Jedi가 될 수 있을까요? ㅋㅋㅋ.


-참고자료

1. Expert F#, Don Syme, Adam Granicz,Antonio Cisternino, Apress

Welcome to F#(11) - 차별을 권장하는 언어인거임?!?!

F# 2009. 7. 8. 22:51 Posted by 알 수 없는 사용자

- 이말이 사실인게냐

discriminate는 (1)여러가지가 있을때 그것들이 서로 다르다는 걸 인식할 수 있다는 의미 (2) 특정한 단체의 사람들에게 against해서 쓰일때는 그 사람들에게 뭔가 부당하게 불이익이나 이익을 주는 의미를 가지고 있습니다.

즉, 둘다 차이에 근거한 의미인데요 첫째는 구별정도가 되겠고 둘째는 차별정도가 되겠네요. F#은 Discriminated Union(이하 union)이라는 아주 쓸모있는 기능을 가지고 있습니다. 그렇다면 F#은 차별을 권장하는 언어로서 인류발전에 그닥 긍정적이지 못한 언어일까요? 다행히 F#에서의 Discriminated는 첫번째인 구별의 의미로 쓰입니다.(뭐가 다행이지?-_-)


- 영단어엔 관심없는거임. 소스내놓으라해.

union의 형태는 아래와 같습니다. 


  type type-name =
   | case-identifier1 [of type1 [ * type2 ...]
   | case-identifier2 [of type3 [ * type4 ...] 


type키워드에 타입이름이 따라오고요 그리고 등호기호, 그리고 수직파이프(|) 문자로 나눠지는 식별자와 식별자의 타입리스트가 이어집니다. 저기서 각각의 식별자(case-identifier)를 discriminator(이하 구분자)라고 하는군요. 바로 차별구분자인거죠. 사칙연산 계산기를 구현하는 짧막한 코드를 완성해가면서 알아볼까요?


 type Expr =
    | Num of float
    | Add of Expr * Expr
    | Sub of Expr * Expr
    | Mul of Expr * Expr
    | Div of Expr * Expr


위 코드는 간단한 사칙연산에 필요한 구분자를 union을 이용해서 선언한 코드입니다. 다섯개의 구분자를 정의하고 있는데요. Num은 그냥 float타입이고, 나머지는 float * float인 tuple타입입니다. 나머지코드를 보시기전에 이 union만 가지고 이야기 해보도록 하죠. 이걸 어케 쓸까요? 


 

위의 그림을 보시면, 저렇게 쓰는거구나~ 하고 아실 수 있습니다. 구분자의 이름을 주고 괄호안에 구분자의 타입에 해당하는 값을 넣어주면 되는거죠. 그러면 Expr타입의 Num이라는 구분자이고 값은 float타입인 5.0을 가지고 있구나~ 하고 알마먹는 것이죠. Add도 마찬가지로 이해할 수 있습니다. 그리고 위의 union의 정의에 이어서 계산에 쓸 수식을 하나 정의해보죠. 

 let expr = Mul(Add(Num(5.0), Num(6.0)), Div(Num(5.0), Num(2.0))) // (5+6) * (5 /2)


주석에 나와있듯이 (5+6) * (5/2)를 위의 구분자로 표현한 수식입니다. 그러면, union을 이렇게 선언해서 구분자를 저렇에 선언한다는건 알겠는데, 저걸 어따써먹는건지 하는 궁금증이 생깁니다. union과 찰떡궁합인게 바로 pattern matching입니다. 아래의 코드를 보시죠

 let rec Calc expr =
    match expr with
    | Num num -> num
    | Add (e1, e2) -> (Calc e1) + (Calc e2)
    | Sub (e1, e2) -> (Calc e1) - (Calc e2)
    | Mul (e1, e2) -> (Calc e1) * (Calc e2)
    | Div (e1, e2) -> (Calc e1) / (Calc e2)

 
위의 코드는 제귀호출이 가능한 Calc라는 메서드인데요 expr이라는 인자를 하나 받습니다. 그리고 그 expr을 가지고 일치하는 패턴을 찾습니다. 말로 하지 말고 그림으로 하겠습니다! 아래의 그림을 보시져.(잘 안보이심 클릭해서 크게보시길...-_-)
 

 

왼쪽 상단은 F# Interactive에서 입력한 부분이고, 오른쪽 하단은 패턴매칭하는 코드 부분이죠. Num(5.0)의 실제 타입은 "Num 5.0"이고 그게 그대로 "Num num"부분에 매칭이 되면서, num의 값은 5.0이 되는거죠. 패턴매칭의 결과로 (Num 5.0) 은 그냥 5.0을 리턴합니다. 그리고 Add(Num 4.0,Num 9.0)도 각각의 Num이 e1, e2에 매칭이 돼서 Calc를 다시 호출하는 모습입니다. 물론 결과적으로 (Num 4.0) -> 4.0, (Num 9.0) -> 9.0이 리턴되면서 두수의 합이 리턴되겠죠. 그러면 마지막으로 결과를 출력합니다.
 

 Calc expr |> printfn "%f"


위의 코드를 실행한 결과는 아래와 같습니다.
 


제대로 결과가 출력된 게 보이시죠? ㅋㅋㅋ. 그럼 오늘도 지난번 처럼 ildasm을 통해서 IL코드를 둘러볼까 합니다.

 
위 사진을 보시면 지난 포스트의 curry처럼 Expr도 클래스로 선언된걸 확인하실 수 있고, union의 구분자중의 하나인 Num도 Expr내부에 중첩된 클래스로 선언되어 있는걸 보실 수 있습니다. 그리고 Num이 float을 하나 가지는데 그게 num1이라는 이름으로 선언되어있는 걸 IL코드를 통해서도 확인해보실 수 있습니다. 즉, 내부적으로는 구분자별로 클래스를 선언해서 타입으로 검사를 하는걸로 생각할 수 있겠네요. 아래에 보시면 그 타입검사를 위한 메서드들이 선언되어있는걸 확인하실 수 있습니다.
 

 

그리고 마지막으로 MSDN에 나와있는 예제를 하나 보도록 하겠습니다. 

type MilkOption =
   | Nonfat
   | TwoPercent
   | Whole
   | Soy
   | Rice
  
type FlavorOption = string
  
type Brand = string

type Size =
   | Short
   | Tall
  
type TeaFlavor = string
type Shots = int

type CoffeeType =
   | Drip
   | EspressoShot
   | Cappuccino
   | Latte of Size * MilkOption * Shots
   | FlavoredLatte of Size * MilkOption * FlavorOption * Shots

type Drink =
   | Can of Brand
   | Coffee of CoffeeType
   | Tea of TeaFlavor

let drink1 = Can("Coke")
let drink2 = Coffee(Drip)
let drink3 = Coffee(Latte(Tall, Nonfat, 2))
// A single short soy latte with hazelnut
let drink4 = Coffee(FlavoredLatte(Short, Soy, "Hazelnut", 1))

union을 통해서 커피가 캔인지 뽑은 커피인지, 커피이외의 차종류인지부터 시작해서 커피의 종류에서, 사이즈, 샷의횟수등 까지 절묘하게 조합하는 모습을 보실 수 있습니다.

 

- 정리하며

F#은 이렇게 union을 쉽게쓸 수 있는 언어적 특성덕분에 DSL(Domain Specific Language, 다른말로 Language Oriented Progamming이라고도 함)을 잘 지원할 수 있는 능력이 있습니다. 물론 DSL은 그리 쉬운 주제는 아니지만, F#과 함께 천천히 시작해보는 것도 나쁘진 않을 거 같습니다. DSL이나 LOP에 대해서 더 설명을 드릴 수 있으면 좋겠지만, 내공이 허락치 않는군요-_-;;;;;;;;;;


- 참고자료

1.  http://msdn.microsoft.com/en-us/library/dd233226(VS.100).aspx
2.  http://sdasrath.blogspot.com/2009/02/20090220-f-types-discriminated-unions.html
3. Expert F#,  Don Syme, Adam Granicz, Antonio Cisternino, Apress

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

C# 2009. 6. 26. 08:58 Posted by 알 수 없는 사용자
- 대인배 모드

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