[이것이 C#이다] 17. dynamic 형식

ssu_hyun·2022년 5월 5일
0

C#

목록 보기
21/22

Key point

  • dynamic
  • 덕 타이핑
  • COM 객체
  • 파이썬과의 상호 운용성



17.1 dynamic 형식

  • 런타임에 형식 검사가 이루어지는 데이터 형식

    • dynamic을 제외한 C#의 모든 데이터 형식은 컴파일 단계에서 형식 검사 수행
    • 컴파일러가 dynamic 키워드를 만나면 형식 검사를 프로그램 실행 때로 미룸
    class MyClass
    {
       public void FuncAAA()
       {  // Do Nothing   }
    }
    
    class MainApp
    {
        static void Main(string[] args)
        {
            dynamic obj = new MyClass();
            obj.FuncAAA();
            obj.FuncBBB();
            /* FuncBBB() 메소드는 선언되지 않았지만 obj가 dynamic으로 선언되어 컴파일러 형식 검사를 피해 간다.
               만약 dynamic으로 선언되지 않았다면 컴파일 에러가 발생한다. */
        }
    }



덕 타이핑

  • "오리처럼 걷고 오리처럼 헤엄치며 오리처럼 꽉꽉 거리는 새를 봤을 때, 나는 그 새를 오리라고 부른다."
  • 상속을 받지 않아도 기본 클래스와 동일하게 동작하는 클래스(메소드가 동일한 클래스)는 기본 클래스의 형식이라고 보는 것
  • dynamic형식으로 선언하면 형식 검사를 런타임 때로 미룬다는 점 이용
  • 장점
    • 인터페이스를 잘못 설계하면 파생 클래스와 형제 클래스, 심지어는 인터페이스 자체를 수정해야하는데, 덕 타이핑의 경우 상속을 사용하지 않으므로 프로그램의 동작 관련 부분만 수정하면 된다.
  • 단점
    • Visual Studio의 리팩터링 기능을 이용하지 못해 직접 모든 메소드 선언을 찾아서 수정해야 한다.

예제

using System;

namespace DuckTyping
{
    class Duck
    {
        public void Walk()
        { Console.WriteLine( this.GetType() + ".Walk"); }

        public void Swim()
        { Console.WriteLine( this.GetType() + ".Swim"); }

        public void Quack()
        { Console.WriteLine( this.GetType() + ".Quack"); }
    }

    class Mallard : Duck  // 상속
    { }

    class Robot
    {
        public void Walk()
        { Console.WriteLine("Robot.Walk"); }

        public void Swim()
        { Console.WriteLine("Robot.Swim"); }

        public void Quack()
        { Console.WriteLine("Robot.Quack"); }
    }

    class MainApp
    {
        static void Main(string[] args)
        {
            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();
             
             /* Duck, Mallard, Robot 클래스가
                Walk(), Swim(), Quack() 메소드를 구현하고 있으므로
                이 코드는 컴파일도 실행도 문제없이 잘 됨
               
                덕 타이핑 관점에서 보면 해당 코드에서 선언한
                Duck도 오리이고 Robot도 오리이다. */ 
               
            }
        }
    }
}



17.2 COM과 .NET 사이의 상호 운용성

컴포넌트(Component)

  • 프로그래밍에 있어 재사용 및 교체가 가능한 각각의 독립된 모듈
    • 위 그림에서 확인할 수 있듯이 컴포넌트 기반 프로그래밍을 하면 마치 레고 블록처럼 이미 만들어진 컴포넌들을 조합하여 화면을 구성할 수 있다.
    • 장난감에 배터리를 교체할 때 고려해야 할 것은 장난감 단자와 배터리 단자가 맞물려야 한다는 것이다. 그래야 비로소 제 기능을 발휘하고 이 때야 비로소 배터리는 일종의 '장치'가 될 수 있다. 많은 하드웨어 제품의 경우 각각 독립된 기능을 가진 모듈(부품, 자동차를 생각하면 이해하기 쉽다)로 만들어져 이들을 조합하여 제품을 만들고 문제가 발생할 경우 이를 유발하는 모듈을 교체하여 문제를 해결한다. 이렇게 하드웨어처럼 독립적인 기능을 수행하고 추후에 교환될 수 있도록 소프트웨어를 만들기 위해 나온 기술이 컴포넌트 기술이다.
  • 객체지향 언어에 컴포넌트 개념 도입
    • 장난감과 배터리는 각각 독립적으로 문제없이 돌아간다고 가정할 때 상호간의 단자만 규격(표준으로 삼아 따르도록 정해 놓은 수치나 형식)에 맞을 시 어떠한 배터리를 교환해도 장난감은 정상 작동할 것이다. 이러한 규격화된 단자의 개념을 소프트웨어에 적용한 것이 객체지향 언어의 인터페이스와 메소드이다. 인터페이스는 사용자에게 소프트웨어를 쓰기 위한 메소드(=장치)를 공개하고, 규격화된 메소드 환경에서 소프트웨어를 개발할 수 있는 환경을 제공해준다. 컴포넌트 개념을 잘 적용한 소프트웨어란 부품(인터페이스를 구현받는 클래스)만 바꾸어주었을 시 오류없이 잘 작동되는 것을 의미한다.
  • 컴포넌트의 은닉성
    • 컴포넌트는 인터페이스를 통해서만 접근할 수 있으며 컴포넌트 내의 정보는 외부로부터 모두 숨겨진다. 즉 컴포넌트를 사용하기 위해서는 어떤 인터페이스를 사용해야 하는지만을 알 수 있다.
  • 컴포넌트와 클래스, 객체는 다른 개념이다.


    [Reference] https://hanamon.kr/%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-component%EB%9E%80/

COM(Component Object Model)

  • Component 규약을 따르는 Microsoft사가 주체가 되어 만든 컴포넌트 표준 규격
    • Component 규약이란 컴파일러의 종류에 관계없이 이에 따라 제작한 Component는 어떤 컴파일러에서도 그 파일을 사용할 수 있도록 하는 규약이다.
  • Component 규약에 따라 제작한 Component는 어떤 컴파일러에서도 그 파일을 사용할 수 있도록하는 규약이다.


    [Reference]
    https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=sunflowerove&logNo=60025990287
  • .NET의 RCW(Runtime Callable Wrapper)를 통한 COM 사용

    • RCW는 .NET 프레임워크의 Type Library Importer(tlbimp.exe)를 이용해 만듦
    • VS 프로젝트에서 참조에 COM 객체 추가시 IDE가 자동으로 tlbimpexe를 호출해 RCW 생성해준다.
    • RCW는 COM에 대한 프록시(Client와 Server 사이에서 데이터를 전달해주는 서버) 역할을 함으로써 C# 코드에서 .NET 클래스 라이브를 사용하듯 COM API를 사용할 수 있게 해준다.
  • C#과 COM 사이의 상호 운용성을 저하시킨 원인

    1. COM의 메소드 반환값은 실제 형식이 아닌 object 형식
      => C# 4.0에서 dynamic 도입해 해결
    2. COM은 오버로딩을 지원하지 않고 메소드의 선택적 매개변수와 기본값 매개변수 지원
      => C# 4.0에서 메소드의 선택적 인수 기본값 도입해 해결
  • 개선된 선택적 인수와 dynamic 형식은 비주얼 스튜디오가 RCW를 만들 때 사용하여 사용자는 전혀 손대지 않아도 된다.

예제

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

namespace COMInterop
{
    class MainApp
    {
        public static void OldWay(string[,] data, string savePath)
        {
            Excel.Application excelApp = new Excel.Application();

            excelApp.Workbooks.Add(Type.Missing);

            Excel.Worksheet workSheet = (Excel.Worksheet)excelApp.ActiveSheet;

            for (int i = 0; i < data.GetLength(0); i++)
            {
                ((Excel.Range)workSheet.Cells[i + 1, 1]).Value2 = data[i, 0];
                ((Excel.Range)workSheet.Cells[i + 1, 2]).Value2 = data[i, 1];
            }

            workSheet.SaveAs(savePath + "\\shpark-book-old.xlsx",
                Type.Missing,
                Type.Missing,
                Type.Missing,
                Type.Missing,
                Type.Missing,
                Type.Missing,
                Type.Missing,
                Type.Missing);

            excelApp.Quit();
        }

        public static void NewWay(string[,] data, string savePath)
        {
            Excel.Application excelApp = new Excel.Application();

            excelApp.Workbooks.Add();

            Excel._Worksheet workSheet = (Excel._Worksheet)excelApp.ActiveSheet;

            for (int i = 0; i < data.GetLength(0); i++)
            {
                workSheet.Cells[i + 1, 1] = data[i, 0];
                workSheet.Cells[i + 1, 2] = data[i, 1];
            }

            workSheet.SaveAs(savePath + "\\shpark-book-dynamic.xlsx");
            excelApp.Quit();
        }

        static void Main(string[] args)
        {
            string savePath = System.IO.Directory.GetCurrentDirectory();
            string[,] array = new string[,]
            {
                { "뇌를 자극하는 알고리즘", "2009" },
                { "뇌를 자극하는 C# 4.0",   "2011" },
                { "뇌를 자극하는 C# 5.0",   "2013" },
                { "뇌를 자극하는 파이썬 3", "2016" },
                { "그로킹 딥러닝",          "2019" },
                { "이것이 C#이다",          "2018" },
                { "이것이 C#이다 2E",       "2020" }
            };

            Console.WriteLine("Creating Excel document in old way...");
            OldWay(array, savePath);

            Console.WriteLine("Creating Excel document in new way...");
            NewWay(array, savePath);
        }
    }
}



17.3 동적 언어와의 상호 운용성

  • 동적 언어 지원

    • DLR(Dynamic Language Runtime)
      • 동적 언어(Python/Ruby) 실행 플랫폼
      • CLR(Common Language Runtime) 위에서 동작
    • 장점
      • 동적 언어 .NET 플랫폼에서 실행 가능
      • 동적 언어로 만든 객체에 정적 언어(C#/VB)로 접근(실행, 결과 반환) 가능
      • C# 프로그래머의 경우 별도의 학습 없이 그리고 상황에 따라 다르게 파이썬과 루비의 라이브러리를 이용하는 코드를 호스팅할 수 있다.

        호스팅(hosting)

        • 서버컴퓨터의 전체 또는 일정 공간을 이용할 수 있도록 임대해 주는 서비스
        • 주인(host)이 손님을 섬긴다는 뜻
        • CLR=주인, 동적 언어=손님
    • 미리 형식 검사를 할 수 없는 동적 형식 언어의 객체를 C#의 dynamic 형식이 받아냄으로써 해결
  • 동적 언어 호스팅을 위한 C# 코드 클래스

    • C# 호스트 코드에서 게스트 코드를 실행할 때 다양한 방법으로 조합하여 사용할 수 있다.
  • 게스트 코드 실행 방법

    • 소스 코드 "파일"의 경로를 넘겨받아 실행
      ScriptRuntime
      ScriptRuntime runtime = Python.CreateRuntime();
       dynamic result = runtime.ExecuteFile("namecard.py");
    • 문자열에 담긴 동적 언어 코드 실행
      ScriptEngine, ScriptScope, ScriptSource

예제

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

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

namespace WithPython
{
    class MainApp
    {
        static void Main(string[] args)
        {
        	// 동적 언어 코드 실행
            ScriptEngine engine = Python.CreateEngine();
            ScriptScope scope = engine.CreateScope();
            scope.SetVariable("n", "박상현");
            scope.SetVariable("p", "010-123-4566");
			
            // 파이썬 소스 코드 읽어들이기
            ScriptSource source = engine.CreateScriptSourceFromString(
                @"
class NameCard :
    name = ''
    phone = ''

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

    def printNameCard(self) : 
        print self.name + ', ' + self.phone

NameCard(n, p)
");	
			// ScriptSource method : 파이썬 코드 실행하여 결과 반환
            dynamic result = source.Execute(scope);
            result.printNameCard();  // 객체 메소드 호출 가능

            Console.WriteLine("{0}, {1}", result.name, result.phone);  // 필드 접근 가능
        }
    }
}

0개의 댓글