C# dynamic

2025. 3. 5. 00:32개발/C#

이 글은 박상현님의 '이것이 C#이다 개정판'을 참고하여 공부한 내용입니다.


 

대부분의 컴파일러는 최적화를 위해 컴파일 타임에 모든 데이터 형식이 결정되어 바뀌지 않습니다. C#에서는 var를 사용해도 컴파일 타임에 컴파일러가 추론하여 올바른 데이터 형식을 결정하는 것 처럼 말이죠. 그래서 확정된 데이터 형식 덕분에 컴파일된 코드 부분은 오류가 발생하지 않고 더 빠른 속도로 작업을 처리할 수 있습니다. 그런데 컴파일 타임에 데이터 형식을 결정하지 않고 런타임에 결정되는 데이터 형식이 있습니다.

 

Dynamic

앞서 말한 런타임에 데이터 타입이 결정되는 데이터 형식 입니다. 컴파일러는 dynamic 형식에 대한 타입 검사를 하지 않으며, 관련된 모든 메서드 호출과 연산들은 런타임에 확인됩니다.

class MyClass
{
    public void FuncA() { }
}
class Program
{
    static void Main()
    {
        MyClass obj = new MyClass();

        obj.FuncA();
        obj.FuncB();
    }
}

 

CS1061	'MyClass' does not contain a definition for 'FuncB' and no accessible extension method 'FuncB' accepting a first argument of type 'MyClass' could be found (are you missing a using directive or an assembly reference?)

 

위 예시는 컴파일 하게되면 반드시 오류가 발생합니다. 알다시피 FuncB라는 메서드는 MyClass에 선언되지 않았기 때문입니다. 하지만 아래의 예시는 어떨까요.

class MyClass
{
    public void FuncA() { }
}
class Program
{
    static void Main()
    {
        dynamic obj = new MyClass();

        obj.FuncA();
        obj.FuncB();
    }
}

 

Unhandled exception. Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 'MyClass' does not contain a definition for 'FuncB'
   at CallSite.Target(Closure, CallSite, Object)
   at System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid1[T0](CallSite site, T0 arg0)
   at Program.Main() in C:\Users\leebo\source\repos\TestApp\TestApp\Program.cs:line 12

 

실행하면 예외가 발생하긴 하지만, 컴파일은 가능합니다. 즉, 컴파일 타임에 dynamic 개체는 타입 안정성 검사를 하지 않은 것을 알수있습니다.

 

덕 타이핑

dynamic의 특성을 이용하여 덕 타이핑을 사용할 수 있습니다. 덕 타이핑이란 덕 테스트에서 유래되었으며, 기존에는 객체의 타입을 상속 혹은 인터페이스의 구현으로 객체의 타입을 결정했지만, 객체의 타입을 객체가 가진 상태와 동작을 기반으로 결정하는 것을 말합니다.

 

즉, 서로 다른 객체들이 동일한 형식의 메서드를 가지고 있다면 그 객체들은 dynamic 의해 하나의 타입으로써 사용할 수 있습니다.

class Dog { public void Speak() => Console.WriteLine("Bark!"); }
class Cat { public void Speak() => Console.WriteLine("Meow!"); }

class Program
{
    public static void MakeSound(dynamic animal)
    {
        animal.Speak();
    }

    static void Main()
    {
        MakeSound(new Dog());
        MakeSound(new Cat());
    }
}

 

Bark!
Meow!

 

위 예시에서는 Dog과 Cat 객체를 dynamic을 통해 하나의 타입으로 사용하였습니다. 이를 통해 덕 타이핑 구조를 이해할 수 있습니다. 덕 타이핑은 문제에 대한 유연한 해결방법을 제시하지만, 인터페이스를 사용하여 구현하는 방법 또한 좋은 해결방안 입니다. 개발자는 여러가지 해결방안 중 가장 적합한 방법을 고민하고 선택하여 개선해야 합니다.

 

COM 객체 상호작용

COM이란 마이크로소프트에서 개발한 소프트웨어 컴포넌트 기술입니다. 이 기술은 다른 프로그래밍 언어로 작성된 컴포넌트들을 공통 인터페이스를 통해 상호 작용할 수 있도록 지원합니다. 그래서 해당 컴포넌트의 코드를 잘 몰라도 사용방법만 안다면 COM을 통해 간편하게 사용할 수 있습니다.

 

COM을 통해 마이크로소프트 오피스를 자동화 할 수 있고, ActiveX를 제어하며, DirectX로 그래픽 렌더링을 수행하며, Windows API를 사용해 시스템 호출을 할 수도 있습니다.

 

C#에서는 이 COM을 RCW, Runtime Callable Wrapper을 통해 객체로 사용할 수 있습니다. RCW는 .NET이 제공하는 Type Library Importer(tlbimp.exe)을 이용하여 생성합니다. 그리고 COM 객체를 프로젝트에 추가하게 되면 자동으로 IDE는 tlbimp.exe을 사용하여 그 객체에 대한 RCW를 생성해줍니다.  

 

여기서 dynamic 형식은 IDE, Visual Studio가 RCW를 생성할 때 사용합니다. 그래서 사용자는 추가적인 작업이 필요 없이 COM 객체를 사용할 수 있게되었습니다.. 아래에는 COM 객체를 사용한 예시입니다.

using System;
using Excel = Microsoft.Office.Interop.Excel;

class Program
{
    public static void DoExcel(string[] data, string savePath)
    {
        Excel.Application excelApp = new Excel.Application();
        excelApp.Workbooks.Add();
        excelApp._Worksheet workSheet = excelApp.ActiveSheet;
        for (int i = 0; i < data.GetLength(0); i++)
        {
            workSheet.Cells[i + 1, 1] = data[i];
        }
        workSheet.SaveAs(savePath + "\\TestExcel.xlsx");
        excelApp.Quit();
    }
    
    static void Main()
    {
        string savePath = System.IO.Directory.GetCurrentDirectory();
        string[] array = new string[]
        {
            "Alice",
            "Bob",
            "Smith"
        };
        DoExcel(array, savePath);
    }
}

위 예시는 현재 경로에 엑셀파일을 하나 생성하여 1열에 이름들을 삽입하여 저장하는 코드입니다. 이 코드를 실행하기 위해서는 엑셀이 필요합니다. 

 

DLR, Dynamic Language Runtime

이 런타임 환경은 .NET에서 동적 언어(Python, JavaScript, Ruby)를 지원하기 위해 탄생하였습니다. 그리고 DLR은 CLR 위에서 동작하며, 동적 언어로 만들어진 객체를 C# 코드에서 접근할 수 있게 해줍니다.

 

이 때 DLR과 CLR 사이의 상호 운용성 문제를 해결하기 위해 dynamic 형식이 사용되었습니다. 미리 형식 검사를 할 수 없는 DLR의 결과를 CLR에서 C# 코드로 받기 위해서 dynamic 형식을 사용하는 것 입니다.

 

아래는 DLR 기반의 동적 언어 실행을 지원하는 클래스들입니다. 

클래스 설명
ScriptRuntime DLR 기반 스크립트 실행 환경을 관리하는 클래스, 참조된 어셈블리나 전역 객체와 같은 전역 상태를 나타내며 하나의 .NET AppDomain 안에 여려 개의 인스턴스를 만들 수 있습니다.
ScriptScope 스크립트 실행 시 사용할 변수와 객체가 저장되는 네임스페이스 클래스 입니다.
ScriptEngine 특정 언어의 실행 엔진 클래스, 코드를 실행하고 ScriptScope와 ScriptSource를 생성하여 다양한 방법을 제공합니다.
ScriptSource 스크립트 코드 자체를 나타내는 클래스, 읽어들인 소스코드를 다양한 방법으로 실행할 수 있는 메소드들을 제공합니다.
CompiledCode 실행을 위해 미리 컴파일된 스크립트 코드를 뜻합니다. 여러 번 반복해서 실행하는 코드를 나타낼 때 사용됩니다.

 

이 클래스들을 사용하여 게스트 코드(Python, JavaScript, Ruby)를 호스트 코드(C#)에 호스팅 할 수 있습니다.

 

using IronPython.Hosting;
using Microsoft.Scripting.Hosting;

class Program
{
    static void Main()
    {
        ScriptRuntime runtime = Python.CreateRuntime();

        ScriptEngine engine = runtime.GetEngine("python");

        string code = "print('Hello from Python!')";
        engine.Execute(code);
    }
}

 

Hello from Python!

 

위 예시는 DLR의 주요 클래스를 사용하여 Python 코드를 C#에서 실행하는 코드입니다. ScriptRuntime과 ScriptEngine을 통해 Python 실행 환경을 구성한 후 문자열로 코드를 넣어 실행한 것 입니다. 이 코드를 실행하기 위해서는 NuGet 패키지 관리자를 통해 IronPython 패키지를 설치해야 합니다.

 

using IronPython.Hosting;
using Microsoft.Scripting.Hosting;

class Program
{
    static void Main()
    {
        ScriptEngine engine = Python.CreateEngine();
        ScriptScope scope = engine.CreateScope();
        scope.SetVariable("n", "Alice");
        scope.SetVariable("a", "24");

        ScriptSource source = engine.CreateScriptSourceFromString(
            @"
class Person :
    name = ''
    age = ''

    def __init__(self, name, age) :
        self.name = name
        self.age = age

    def printPerson(self) :
        print(self.name + ', ' + self.age)

Person(n, a)
");

        dynamic result = source.Execute(scope);
        result.printPerson();

        Console.WriteLine($"{result.name} is {result.age} years old");
    }
}

 

Alice, 24
Alice is 24 years old

 

위 예시는 ScriptSource를 사용하여 Python 코드를 작성한 뒤 실행하고, 결과를 dynamic 형식으로 받아 사용하는 코드 입니다. 동일하게 이 코드를 실행하기 위해서는  NuGet 패키지 관리자를 통해 IronPython 패키지를 설치해야 합니다.

'개발 > C#' 카테고리의 다른 글

C# 스레드와 태스크  (0) 2025.03.19
C# 입출력 작업  (0) 2025.03.09
C# 애트리뷰트  (0) 2025.02.25
C# 리플렉션  (0) 2025.02.23
C# LINQ  (0) 2025.02.21