공부/C#

이것이 C#이다 Chapter 17 dynamic 타입

bokob 2024. 3. 17. 20:43

1. dynamic 타입

dynamic 타입도 데이터 타입이다. 타입 검사를 하는 시점이 프로그램 실행 중이다.

class MyClass
{
    public void FuncAAA(){}
}

class MainApp
{
    static void Main(string[] args)
    {
        MyClass obj = new MyClass();
        obj.FuncAAA();
        obj.FuncBBB(); // 정의되어 있지 않으므로 컴파일 에러
    }
}

MyClass 클래스에 FuncBBB() 메소드를 선언하지 않았기 때문에 컴파일되지 않는다.

 

class MyClass
{
    public void FuncAAA(){}
}

class MainApp
{
    static void Main(string[] args)
    {
        dynamic obj = new MyClass();
        obj.FuncAAA();
        obj.FuncBBB(); // obj가 dynamic으로 선언되어 컴파일러 타입 검사를 피해 간다.
    }
}

컴파일 에러가 나지 않고, 실행된다.

컴파일러가 dynamic 키워드를 만나면 프로그램을 실행할 때 타입 검사를 하도록 미루기 때문이다. 

 

C# 컴파일러가 제공하는 '강력한 형식 검사'의 이점과 dynamic 타입의 특징이 상충한다.

dynamic이 주는 이점도 분명히 있지만, 타입 검사를 컴파일할 때 같이 하지 않는다는 점 때문에 논란이 많았다.

dynamic 키워드는 타입 검사를 프로그램을 실행할 때 하도록 미루겠다는 것뿐이지. 하지 않겠다는 것이 아니다.

'강력한 타입 검사'는 dynamic 키워드가 사용된 곳에서도 여전히 유효하다.

프로그래머는 컴파일 한 번 해보고 프로그램을 배포하는 것이 아니다.

여러 단계의 테스트를 거쳐서 프로그램에 오류가 없음을 확인한 후 배포한다.

dynamic 타입으로 인해 타입 검사를 못 하는 문제는 바로 이 테스트 단계에서 발견하여 제거할 수 있다.

 

1. 덕 타이핑

"오리처럼 걷고 오리처럼 헤엄치며 오리처럼 꽉꽉거리는 새를 봤을 때, 나는 그 새를 오리라고 부른다."

미국의 시인인 제임스 휘트컴 라일리의 시에서 인용된 것으로, 덕 타이핑(Duck Typing)을 가장 잘 설명하는 문장이다.

 

class Duck
{
    public void Walk()
    {Console.WriteLine("Duck.Walk");}
    
    public void Swim()
    {Console.WriteLine("Duck.Swim");}
    
    public void Quack()
    {Console.WriteLine("Duck.Quack");}
}

class Mallard : Duck // Mallard(청동오리)는 Duck으로부터 상속받으므로 Duck이라고 인정할 수 있음
{
    // ...
}

class Robot // Robot도 오리
{
    public void Walk()
    {Console.WriteLine("Robot.Walk");}
    
    public void Swim()
    {Console.WriteLine("Robot.Swim");}
    
    public void Quack()
    {Console.WriteLine("Robot.Quack");}
}

덕 타이핑에서 어떤 타입이 오리로 인정받으려면 오리처럼 걷고, 오리처럼 헤엄치고, 오리처럼 꽉꽉거리면 된다.

그 타입이 어느 타입으로부터 상속받는지 전혀 중요하지 않다.

덕 타이핑 관점에서 보면 Robot도 오리다.

 

하지만, C# 컴파일러는 Duck, Mallard는 오리로 인정해도 Robot은 오리로 인정하지 않는다.

억지로 Duck 타입의 배열을 선언하고 위 클래스들의 인스턴스를 각각 넣어 초기화하면 Robot은 Duck 타입이 아니라는 오류 메시지를 보일 것이다.

이 경우는 dynamic 타입의 타입 검사를 실행할 때로 미룬다는 점을 이용해 해결할 수 있다.

 

dynamic[] arr = new dynamic[] {new Duck(), new Mallard(), new Robot()};

foreach (dynamic duck in arr)
{
    Console.WriteLine(duck.GetType());
    duck.Walk();
    duck.Swim();
    duck.Quack();
    
    Console.WriteLine();
}

각각의 클래스가 Walk(), Swim(), Quack() 메소드를 구현하고 있으므로 컴파일도 실행도 문제없이 동작한다.

 

덕 타이핑 사용 이유

인터페이스 상속을 이용하면 비슷하게 할 수 있겠지만, 인터페이스 설계를 위해서는 추상화가 잘되어야 한다.

이는 연습과 경험을 많이 해봐야 하고, 잘못 설계했다가 나중에 파생 클래스를 수정해야할 일이 생기면 위로는 인터페이스를 수정하고 아래로는 파생 클래스들, 옆으로는 형제 클래스들을 수정해야하는 일이 생긴다.

덕 타이핑은 이런 문제를 만났을 때 좀 더 유연하게 해결할 수 있도록 도와준다.

 

덕 타이핑 단점

비주얼 스튜디오의 리팩터링 기능을 이용할 수 없다. 예를 들어 Walk()를 Run()으로 바꾸고 싶어도 직접 선언한 곳과 사용하고 있는 코드로 이동해 수정해야 한다.

2. COM과 .NET 사이의 상호 운용성을 위한 dynamic 타입

COM(Component Object Model)

  • 마이크로소프트가 개발한 소프트웨어 구성 요소들의 응용 프로그램 이진 인터페이스 표준
  • OLE, ActiveX, COM+ 같은 파생 표준들이 모두 COM을 바탕으로 만들어짐

C#을 비롯한 .NET 언어들은 RCW(Runtime Callable Wrapper)를 통해서 COM 컴포넌트를 사용할 수 있다.

 

RCW(Runtime Callable Wrapper)

  • .NET이 제공하는 Type Library Importer(tlbimp.exe)를 이용해서 만들 수 있는데, 비주얼 스튜디오를 사용해서 COM 객체를 프로젝트 참조에 추가하면 IDE가 자동으로 tlbimp.exe를 호출해 RCW를 만들어준다.
  • COM에 대한 프록시 역할을 함으로써 C# 코드에서 .NET 클래스 라이브러리를 사용하듯 COM API를 사용할 수 있게 해줌

(게임 개발에 쓸모 없는 부분이랑 생략)

3. 동적 언어와 상호 운용성을 위한 dynamic 타입

DLR(Dynamic Language Runtime)

  • 동적 언어(파이썬, 루비 등)를 실행할 수 있도록 하는 플랫폼
  • CLR 위에서 동작하며, 동적 언어를 실행할 수 있음
  • 동적 언어의 코드에서 만들어진 객체에 C#이나 VB 같은 정적 언어의 코드에서 접근할 수 있게 해준다. 즉, C# 코드에서 직접 파이썬이나 루비 코드를 실행하고 그 결과를 받아볼 수 있다는 것

(게임 개발에 쓸모 없는 부분이랑 생략)