출처 : http://blog.tobegin.net/36 / Writer by 정은성
04| Dynamic 유형 객체 #2 : Dynamic lookup
말 못할 일상생활에 빠져 포스팅이 늦어버렸네요. 흑흑..어쨌든 프로젝트 투입전까지 포스팅을 마무리 짓겠습니다.
오늘 다뤄볼 내용은 바로 Dynamic lookup인데요. Dynamic lookup과 함께 C# 4.0에서 가장 큰 변화라고 하면 바로 Dynamic 키워드 도입이라고 할수 있습니다. 이번 포스팅에서는 Dynamic이 어떤 의미를 가지고 있고 Dynamic lookup을 통해 duck Typing의 프로그래밍 스타일에 대해 알아보도록 하는 시간을 가져보도록 하겠습니다.
Dynamic에 대해서 생각을 해보시면 무언가 굉장히 추상적이다 라는 의미를 알 수 있습니다.
C# 4.0의 PM인 Mads Torgersen이 기술한 New Features in C# 내용을 인용한다면 다음과 정의 할 수 있습니다.
- The Dynamic Type
- Dynamic Operations
- Runtime Lookup
가. COM objects(COM IDispatch)
나. Dynamic Objects
(IDynamicObject, IronPython, IronRuby, HTML DOM)
- Plain object(.NET object reflection etc)
- Dynamic Operations
- Runtime Lookup
가. COM objects(COM IDispatch)
나. Dynamic Objects
(IDynamicObject, IronPython, IronRuby, HTML DOM)
- Plain object(.NET object reflection etc)
위 표를 보시면 동적 유형, 동적 연산, 런타입 룩업, 일반 개체로 나누어져 있습니다.
이번 포스팅에서는 전부 살펴볼수 없기에 그 중동적 유형, 동적 연산, 런타임 룩업에 대해서 살펴보도록 하겠습니다.
* 동적 유형(Dynamic Type)
C# 4.0에서 새로운 정적 타입의 동적 키워드를 소개를 하고 있습니다. 동적 유형의 오브젝트를 사용할 때는 런타임 에서만 그 타입을 결정 할 수 있는데요. 먼저 코드를 살펴보도록 하겠습니다.
[코드 1] Dynamic Type Sample
dynamic d = GetDynamicObject(...);
d.M(7);
d.M(7);
겉모습으로 볼때 반환하는 메소드 형태와 큰 차이는 없어 보입니다. 그러나 이미 여기서 코드를 보시면서 의문을 가지신분들이 계실것 같은데요. 바로 dynamic 키워드와 GetDynamicObject 메소드의 관계가 굉장히 의심 스럽습니다.
GetDynamicObject 이름을 가진 메소드는 어떤 타입을 가지고 반환하는지 알수가 없을 뿐만 아니라 dynamic이라는 키워드의 변수에 할당하는 모습이 예사롭지 않다는 사실을 알수 있습니다.
C# 4.0 컴파일에서는 GetDynamicObject 메소드와 같이 어떤 이름과 어떤 인수를 가진 메소드든 간에 동적 타입에게 할당할 경우 허용을 하고 있습니다. 이유는 dynamic 형식은 정적 형식이지만 정적형식 검사에서 제외됩니다.
이형식의 타입을 사용하는 개체에서 값을 가져오는 소스가 COM API, Ironpython과 같은 동적언어, DOM 개체모델 등 프로그램 내부인지 다른 위치인지 또는 리플렉션이라도 신경 쓰지 않으셔도 됩니다.
여기서 동적 타입 개체는 바로 d인데요.
개체 d는 어떤 메소드를 호출 하려고 액션을 취하고 있습니다.
개체 d는 어떤 메소드를 호출 하려고 액션을 취하고 있습니다.
앞서 말씀을 드렸듯이 동적 유형의 오브젝트를 사용할때에는 런타임에서만 그 타입을 결정을 할수 있기 때문에 컴파일 시점에는 알수는 없습니다. 결국 런타임시점에서 d는 M에 관련된 가장 적합한 메소드를 찾게 되며 매개변수로 전달되는 심볼 유형이 숫자이기때문에 int형 매개변수를 가진 메소드를 호출하게 됩니다.
그래도 이해가 되지 않으셨다면 간단한 예제 코드를 살펴보시면 이해가 될것 같네요.
[코드 2] Dynamic Type Sample
위 코드는 [코드 1]의 d.M(7); 코드 구문을 이해 하고자 간단한 코드를 보였는데요.
dynamic d = 7 수식 같은경우 RValue인 숫자 심볼 7을 LValue인 Dynamic d 변수에 할당하는 모습입니다.
이는 컴파일 타임에서 암시적으로 int형 타입으로 캐스팅하는 행위로 재해석할수 있습니다.
int i = d 수식은 위 수식과 달리 런타임에서 동적타입인 d 변수에 대해서 암시적으로 int형으로 캐스팅이 되어집니다.
* 동적 조작(Dynamic Operations)
Method 호출 뿐만 아니라, Field 접근, Indexer 및 Operator 호출, Constructor 호출, 대리자 형식까지 동적으로 재정의할 수 있습니다.다음 코드를 보시면 동적 조작에 대한 예제에 대해 알수 있습니다.
[코드 3] Dynamic Operations Sample
C# 컴파일러의 역할은 단순히 "무엇을 동적"으로 수행되는지에 대해 필요한 정보를 패키지할 뿐만 아니라 런타임에서 그정보가 무엇의 정확한 의미를 가지는지 실제 개체를 동적으로 부여됩니다.
* Dynamic 뭐가 좋아?
그래도 이해가 되지 않으셨다면 간단한 예제 코드를 살펴보시면 이해가 될것 같네요.
[코드 2] Dynamic Type Sample
dynamic d = 7;
int i = d;
int i = d;
위 코드는 [코드 1]의 d.M(7); 코드 구문을 이해 하고자 간단한 코드를 보였는데요.
dynamic d = 7 수식 같은경우 RValue인 숫자 심볼 7을 LValue인 Dynamic d 변수에 할당하는 모습입니다.
이는 컴파일 타임에서 암시적으로 int형 타입으로 캐스팅하는 행위로 재해석할수 있습니다.
int i = d 수식은 위 수식과 달리 런타임에서 동적타입인 d 변수에 대해서 암시적으로 int형으로 캐스팅이 되어집니다.
* 동적 조작(Dynamic Operations)
Method 호출 뿐만 아니라, Field 접근, Indexer 및 Operator 호출, Constructor 호출, 대리자 형식까지 동적으로 재정의할 수 있습니다.다음 코드를 보시면 동적 조작에 대한 예제에 대해 알수 있습니다.
[코드 3] Dynamic Operations Sample
dynamic d = GetDynamicObject(...);
d.M(7); // Method 호출의 예
d.f = d.P; // 해당 Field/Property에 대해 Getter/Setter의 예
d["one"] = d["two"]; // Indexer의 예
int i = d + 3; // Operator 호출의 예
string s = d(5,7); // Delegate를 통해 호출의 예
var c = new C(d); // Constructor 호출의 예
d.M(7); // Method 호출의 예
d.f = d.P; // 해당 Field/Property에 대해 Getter/Setter의 예
d["one"] = d["two"]; // Indexer의 예
int i = d + 3; // Operator 호출의 예
string s = d(5,7); // Delegate를 통해 호출의 예
var c = new C(d); // Constructor 호출의 예
C# 컴파일러의 역할은 단순히 "무엇을 동적"으로 수행되는지에 대해 필요한 정보를 패키지할 뿐만 아니라 런타임에서 그정보가 무엇의 정확한 의미를 가지는지 실제 개체를 동적으로 부여됩니다.
* Dynamic 뭐가 좋아?
그럼 dynamic 키워드가 뭐가 좋은데? 라고 질문을 주실 수 있는데요. 하위 버전의 C# 코드와 비교 해보겠습니다.
[코드 4] Dynamic Sample
[코드 4-1] 정적 타입 유추
Calculator calc = new Calculator();
int sum = calc.Add(10, 20);
[코드 4-2] 컴파일 타입 유추
var calc2 = new Calculator();
int sum2 = calc2.Add(10, 20);
[코드 4-3] 런타임 타입 유추
dynamic calc = GetCalculator();
int sum = calc.Add(10, 20);
Calculator calc = new Calculator();
int sum = calc.Add(10, 20);
[코드 4-2] 컴파일 타입 유추
var calc2 = new Calculator();
int sum2 = calc2.Add(10, 20);
[코드 4-3] 런타임 타입 유추
dynamic calc = GetCalculator();
int sum = calc.Add(10, 20);
첫번째 코드[코드 4-1]는 가장 일반적인 정적 타입 유추 코드이며 두번째[코드 4-2] var 키워드의 특징을 이용하여 존재하지 않지만 컴파일 타임에서 Calculator라는 클래스임을 판단하여 타입을 유추를 하게 되고 해당 두 숫자를 합하여 sum varable에 값을 반환을 하게 됩니다. 세번째 코드[코드 4-3]에서는 dynamic 타입을 사용하였는데요 dynamic 타입은 실행시간을 지연하여 런타임에서 타입을 유추를 하게 됩니다. 처음 Dynamic 에 대해서 설명을 드렸듯이 정적타입 검사에서 Add라는 메서드는 제외가 되므로 런타임에서 바인딩 되어 동적으로 호출합니다.
여기서 만약에 dynamic 키워드가 없다면 어땠을까요?
dynamic 키워드가 없었다면 다음과 같이 작성을 해야합니다.
[코드 5] Reflection Sample
object calc = GetCalculator();
Type calcType = calc.GetType();
object res = calcType.InvokeMember("Add", BindingFlags.InvokeMethod,
null, new object[] { 10, 20 }); int sum = Convert.ToInt32(res);
Type calcType = calc.GetType();
object res = calcType.InvokeMember("Add", BindingFlags.InvokeMethod,
null, new object[] { 10, 20 }); int sum = Convert.ToInt32(res);
GetCalculator 클래스가 다른 외부의 객체 모델(Object Model) 은 아니지만 외부 객체라고 가정을 해보겠습니다.
근데 성능상 좋지는 않아보입니다. 왜냐하면 Variant를 위해 불필요하게 object 형식을 다시 해당하는 타입으로 cast을 하고 있는데요. 이는 오버헤드를 발생하게됩니다. 더구나 코드도 어렵네요.
여러분이 객관적으로 보실 때 선택을 한다면 reflection을 사용하시겠습니까?
아니면 dynamic 키워드를 사용하시겠습니까?
이번 포스팅에서 두 특징에 대해서 상세히 다를 순 없지만 간단히 설명을 드리자면 속도면에서 사실상 리플렉션이 더 빠른 결과를 나타내고 있습니다. 그리고 dynamic은 리플렉션에 비해 느리지만 리플렉션의 단점이였던 많은 메모리 소모와 호환성 문제를 높은 수준으로 보장하고 있습니다
여러분들의 코드에 동적으로 무언가를 꼭 해야 한다면 필요한 시점에서 사용하시기를 권장합니다.
* Duck Typing(dynamic lookup)
Dynamic 키워드를 도입을 하면서 dynamic lookup이라는 말과 함께 새로운 동적 프로그래밍 스타일이 이슈가 되었는데요
바로 Duck Typing 이라는 프로그래밍 스타일이 C#에서 가능하게 되었습니다.
위키디피아에서 다음과 같이 정의가 되어 있습니다.
If it walks like a duck and quacks like a duck, I would call it a duck
위 문장을 인용을 한다면 오리처럼 행동하면 오리라 부를 수 있다는 의미로써,
문자열처럼 다루게 된다면 그것이 객체이든 뭐든 상관하지 않고 그객체를 문자열처럼
취급해도 된다라는 이야기입니다.
흔히 알고 있는 상속이나 구현과 같이 해당 객체의 부모나, 인터페이스의 영향아닌 그 오브젝트가 가진 특징에 의해 역활을 결정 되는거죠.
즉, Duck Typing과 같이 룩업을 하게 된다면 어떤 클래스나 인터페이스 타입의 오브젝트인지에 상관없이 호출시 해당 오브젝트 존재하기만하면 사용 할 수 있습니다.
* Dynamic Lookup과 함께 Duck Typing 스타일이 이슈가 된 이유?
어떤 어플리케이션을 만들고 테스트를 해야할 때 일반적으로 mock오브젝트를 만들고 테스트를 하죠? 자바와 같은 Static Typing 언어에서는 mock object를 만들기 위해서 실제 사용할 오브젝트와 동일한 인터페이스를 구현을 해야 합니다. 다형성을 활용하여 구현을 할 수 있지만 만약 인터페이스 내에 메소드가 많다면 테스트와 관계없는 메소드까지 구현을 해야 한다는 단점이 있습니다. 다행히 IDE의 도움으로 쉽게 생성을 할 수 있지만 한 메소드를 테스트하기 위해서 모든 메소드를 구현을 해야한다면 좋지 않겠죠? 정말 비효율적인데요. 이런 바탕으로 프로그래밍 스타일이 이슈가 되게 되었습니다.
다음 데모를 보시면서 " Dynamic Lookup이 이렇게 사용하는구나! " 라고 이해가 될것 같네요.
[코드 6] Dynamic Lookup Sample
var people = new { Name = "정은성", Age = 27 };
PeopleInfo(people);
public static void PeopleInfo(dynamic d)
{
Console.WriteLine( "이름: {0}, 나이: {1}" , d.Name, d.Age);
}
PeopleInfo(people);
public static void PeopleInfo(dynamic d)
{
Console.WriteLine( "이름: {0}, 나이: {1}" , d.Name, d.Age);
}
코드 6번과 동일한 퍼포먼스를 수행하기 위해서는 리플렉션을 이용하거나 해당 매개변수와 동일한 인터페이스를 가지거나 멤버변수를 지정을 해줘야만 PeopleInfo 메소드를 수행을 할수 있었습니다. Dynamic 도입으로 인해 늦은 바인딩이 가능한 모습을 볼수 있습니다. 다음 데모를 통해 Duck Typing에 대해서 더 많은 이해를 할 수 있습니다.
[코드 7] ExpandoObject Sample
class Program {
static void Main(string[] args) {
dynamic people = new ExpandoObject();
people.Name = "정은성";
people.Age = new Sameple(); //new Sameple(); or 27
people.Run = new Action(() => Console.WriteLine("Run 메소드 런타임에서 실행 "));
printInfo(people);
people.Run();
}
static void printInfo(dynamic info) {
Console.WriteLine(string.Format("이름{0}, 나이는 {1} 입니다.", info.Name, info.Age)); }
}
class Sameple {
private int Age = 27;
public override string ToString() {
return this.Age.ToString(); }
}
static void Main(string[] args) {
dynamic people = new ExpandoObject();
people.Name = "정은성";
people.Age = new Sameple(); //new Sameple(); or 27
people.Run = new Action(() => Console.WriteLine("Run 메소드 런타임에서 실행 "));
printInfo(people);
people.Run();
}
static void printInfo(dynamic info) {
Console.WriteLine(string.Format("이름{0}, 나이는 {1} 입니다.", info.Name, info.Age)); }
}
class Sameple {
private int Age = 27;
public override string ToString() {
return this.Age.ToString(); }
}
다음 코드 좀 주목해야할 부분입니다.
people.Age = new Sameple(); //new Sameple(); or 27
일반적인 코드라면 동일한 타입이 아닌 이상 오류가 날수 밖에 없는데요.
C# 4.0에서는 Dynamic Lookup 가능하기 때문에 런타임에서 수행가능한 코드라면 그것이
숫자든 문자열든 관계없이 수행을 할수 있습니다.
people.Run = new Action(() => Console.WriteLine("Run 메소드 런타임에서 실행 "));
람다 문장을 활용하여 해당 메소드를 룩업한 모습을 볼수 있습니다.
일반적인 코드라면 동일한 타입이 아닌 이상 오류가 날수 밖에 없는데요.
C# 4.0에서는 Dynamic Lookup 가능하기 때문에 런타임에서 수행가능한 코드라면 그것이
숫자든 문자열든 관계없이 수행을 할수 있습니다.
people.Run = new Action(() => Console.WriteLine("Run 메소드 런타임에서 실행 "));
람다 문장을 활용하여 해당 메소드를 룩업한 모습을 볼수 있습니다.
[코드 8] IronPython Sample ( ※ DevDays2010에서 선보였던 예제코드를 재구성 )
ScriptRuntime py = Python.CreateRuntime();
dynamic python = py.UseFile("Calculator.py");
dynamic calc = python.GetCalculator();
for (int i = 0; i < 10; i++)
{
var sum = calc.Add(DateTime.Today, TimeSpan.FromDays(i));
Console.WriteLine(sum);
}
dynamic python = py.UseFile("Calculator.py");
dynamic calc = python.GetCalculator();
for (int i = 0; i < 10; i++)
{
var sum = calc.Add(DateTime.Today, TimeSpan.FromDays(i));
Console.WriteLine(sum);
}
[소스 참고]Hello .NET Framework 4 세미나[세션3] 발표자료와 소스 : http://blog.tobegin.net/35
참고 문헌
1. PDC2008 : The Future of C# - Anders Hejlsberg
: http://channel9.msdn.com/pdc2008/TL16/
2. dynamic(C# 참조)
: http://msdn.microsoft.com/ko-kr/library/dd264741.aspx
3. The Dynamic Keyword in C# 4.0 - CodeProject
: http://www.codeproject.com/KB/cs/TheDynamicKeyword.aspx
4. BOO -Duck Typing
: http://boo.codehaus.org/Duck%2BTyping
포스팅을 마치며...
맨날 바쁘다며 포스팅을 게으르게 올리고 있는데요. 열심히 하겠습니다. 흑흑..
Dynamic Lookup에 관한 포스팅은 여기까지 하도록 하겠습니다.
더 궁금하신점이 있으시면 댓글 혹은 http://blog.tobegin.net/notice/1 프로필정보를 통해 문의 바랍니다.
다음 회차에서는 Optional and Named Parmeters에 대해서 살펴보도록 하겠습니다.
무더위 준비 잘하시고 좋은 하루 되세요 : )
감사합니다.
정은성 드림
출처 : http://blog.tobegin.net/36 / Writer by 정은성