[C# 4.0] 동적 객체에 대한 접근

지난번에 올린 Dynamic Programming에서, COM에 대한 Dynamic Binding을 통하여 웹 브라우저를 마치 자바스크립트로 제어하는것과 같은 편리성을 확인할 수 있었습니다. 그리고 여기에 더불어서, Dynamic Programming에 대한 재미있는 내용을 하나 더 다루어보고자 합니다.


 


System.Dynamic 네임스페이스는 닷넷 프레임워크 4.0에서 새로 소개된 네임스페이스로, Dynamic Language Runtime을 활용할 수 있도록 기본 기능들에 대한 사항들을 포함하고 있습니다. 여기서 우리가 알아두면 유용한 리소스 두 가지를 살펴보기로 하겠습니다.


 


1. DynamicObject의 재정의


 


System.Dynamic.DynamicObject 클래스는, System.Dynamic.IDynamicMetaObjectProvider 인터페이스를 구현하는 보통의 클래스입니다. 다만, 생성자가 protected로 보호되어있고, 10종류가 넘는 가상 메서드를 제공하고 있습니다.


 


DynamicObject는 Dynamic Programming에 있어서 필요한 기능들을 거의 모두 내장하고 있습니다. 가령, 컴파일 타임에서 정의되지 않은 멤버를 두고서 단항/이항 연산자를 재정의하거나, 멤버를 가져오거나, 멤버를 설정하거나, 멤버를 삭제하거나, 멤버를 호출하는 등 보편적인 객체 지향 프로그래밍 환경에서 취할 수 있는 액션들을 모두 재정의할 수 있도록 추상화하고 있습니다.


 


여기서 주목할 점은, DynamicObject의 기능을 확장하기 위하여 Expression Parser를 같이 이용한다는 점입니다. 앞 예제에서 보여드린, 마치 JavaScript처럼 코드를 자유자재로 C# 코드를 확장한 예시를 예로 들어본다면, Dynamic 컨텍스트 내에 서술한 C# 코드 조각은 Expression Parser에 의하여 처리되어 Expression 클래스의 인스턴스로 변환됩니다. 이렇게 변환된 객체는 DynamicObject에 미리 정의된 메서드들에 전달되는 것입니다.


 


다음은 Expression 객체를 사용하는 예시 코드입니다. (MSDN에서 발췌함)


 

using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq.Expressions;

// The class derived from DynamicObject.
public class DynamicNumber : DynamicObject
{
// The inner dictionary to store field names and values.
Dictionary dictionary
= new Dictionary();

// Get the property value.
public override bool TryGetMember(
    GetMemberBinder binder, out object result)
{
    return dictionary.TryGetValue(binder.Name, out result);
}

// Set the property value.
public override bool TrySetMember(
    SetMemberBinder binder, object value)
{
    dictionary[binder.Name] = value;
    return true;
}

// Perform the binary operation. 
public override bool TryBinaryOperation(
    BinaryOperationBinder binder, object arg, out object result)
{
    // The Textual property contains the textual representaion 
    // of two numbers, in addition to the name 
    // of the binary operation.
    string resultTextual =
        dictionary["Textual"].ToString() + " "
        + binder.Operation + " " +
        ((DynamicNumber)arg).dictionary["Textual"].ToString();

    int resultNumeric;

    // Checking what type of operation is being performed.
    switch (binder.Operation)
    {
        // Proccessing mathematical addition (a + b).
        case ExpressionType.Add:
            resultNumeric =
                (int)dictionary["Numeric"] +  
                (int)((DynamicNumber)arg).dictionary["Numeric"];
            break;

        // Processing mathematical substraction (a - b).
        case ExpressionType.Subtract:
            resultNumeric =
                (int)dictionary["Numeric"] - 
                (int)((DynamicNumber)arg).dictionary["Numeric"];
            break;

        // In case of any other binary operation,
        // print out the type of operation and return false,
        // which means that the language should determine 
        // what to do.
        // (Usually the language just throws an exception.)
        default:
            Console.WriteLine(
                binder.Operation + 
                ": This binary operation is not implemented");
            result = null;
            return false;
    }

    dynamic finalResult = new DynamicNumber();
    finalResult.Textual = resultTextual;
    finalResult.Numeric = resultNumeric;
    result = finalResult;
    return true;
}

}

class Program
{
static void Main(string[] args)
{
// Creating the first dynamic number.
dynamic firstNumber = new DynamicNumber();

    // Creating properties and setting their values
    // for the first dynamic number.
    // The TrySetMember method is called.
    firstNumber.Textual = "One";
    firstNumber.Numeric = 1;

    // Printing out properties. The TryGetMember method is called.
    Console.WriteLine(
        firstNumber.Textual + " " + firstNumber.Numeric);

    // Creating the second dynamic number.
    dynamic secondNumber = new DynamicNumber();       
    secondNumber.Textual = "Two";
    secondNumber.Numeric = 2;
    Console.WriteLine(
        secondNumber.Textual + " " + secondNumber.Numeric);


    dynamic resultNumber = new DynamicNumber();

    // Adding two numbers. The TryBinaryOperation is called.
    resultNumber = firstNumber + secondNumber;

    Console.WriteLine(
        resultNumber.Textual + " " + resultNumber.Numeric);

    // Subtracting two numbers. TryBinaryOperation is called.
    resultNumber = firstNumber - secondNumber;

    Console.WriteLine(
        resultNumber.Textual + " " + resultNumber.Numeric);

    // The following statement produces a run-time exception
    // because the multiplication operation is not implemented.
    // resultNumber = firstNumber * secondNumber;
}

}

// This code example produces the following output:

// One 1
// Two 2
// One Add Two 3
// One Subtract Two -1


 


Expression 객체를 받아서, 이항 연산을 수행하는 부분을 살펴보면, 언어의 종류에 관계없이 Expression Parser를 이용하여 처리된 구문 분석 결과를 사용한다는 것을 알 수 있습니다. 즉, C#이나 Visual Basic .NET 같은 기본 런타임 언어 뿐만 아니라, IronPython과 같은 언어에도 대응이 가능함을 뜻합니다. 이러한 기능을 활용하여, 메서드의 추가 없이 언어의 특성 만으로 원하는 기능까지 이끌어내는 것이 좀 더 손쉽습니다. 예를 들어, 별도의 메서드 호출 없이 문자열 간 이항 연산 중 더하기 연산에서 HTML에 대응할 수 있도록 <BR /> 태그를 자동으로 덧붙이는 기능을 생각해 볼 수 있을 것입니다.


 


2. Anonymous Type과 ExpandoObject


 


C# 3.0에서는 익명 형식이 도입되었습니다. 이것은 전적으로 LINQ와 같이 동적으로 데이터 형식을 확장해야 하는 시나리오에 최적화된 기술로, LINQ를 위한 새로운 클래스를 작성하는 수고를 덜어주게 됩니다. (Where 절이나 Select절에서 여러 조건을 지정하는 시나리오를 생각하시면 쉽습니다.)


 


그러나 지금 소개하는 ExpandoObject는 어떤 시점에서 임시로 필요한 형식을 개설하는 일과는 별도로, 전형적인 스크립트 언어의 동적 형식을 구현한 것으로, 익명 형식이 컴파일러에 의하여 동적으로 추가되지 않고, 런타임에서 키와 값의 쌍으로 이루어진 Dictionary Type의 객체를 만드는 것이 조금 다릅니다. 이러한 기능 자체는 이미 C# 초기 버전부터 제공되어오던 Hashtable이나 C# 2.0의 Generic 버전의 Dictionary<TKey, TValue> 형식을 이용하는 것과 개념상 유사하지만, 문법적인 관점에서 좀 더 업그레이드 된 형태로 이해할 수 있습니다.


 


아래는 ExpandoObject를 Dynamic Context 아래에서 활용하는 예시입니다.

class Program
{
static void Main(string[] args)
{
dynamic employee, manager;

    employee = new ExpandoObject();
    employee.Name = "John Smith";
    employee.Age = 33;

    manager = new ExpandoObject();
    manager.Name = "Allison Brown";
    manager.Age = 42;
    manager.TeamSize = 10;

    WritePerson(manager);
    WritePerson(employee);
}
private static void WritePerson(dynamic person)
{
    Console.WriteLine("{0} is {1} years old.",
                      person.Name, person.Age);
    // The following statement causes an exception
    // if you pass the employee object.
    // Console.WriteLine("Manages {0} people", person.TeamSize);
}

}
// This code example produces the following output:
// John Smith is 33 years old.
// Allison Brown is 42 years old.


 


그리고 아래의 코드는 Dictionary의 경우와 마찬가지로, ExpandoObject의 모든 멤버들을 Reflection 없이 나열하는 것입니다. (내부적으로 Dictionary와 같은 개체를 사용하여 멤버들을 관리하고 있으므로 Reflection을 이용하지 않는것으로도 볼 수 있습니다.)

dynamic employee = new ExpandoObject();
employee.Name = “John Smith”;
employee.Age = 33;

foreach (var property in (IDictionary)employee)
{
Console.WriteLine(property.Key + “: ” + property.Value);
}
// This code example produces the following output:
// Name: John Smith
// Age: 33


 


ExpandoObject는 문법적으로 Dictionary 컬렉션을 확장한 것으로 이해할 수 있기 때문에, 확정적이지 않은, 변동 가능성이 있는 다수의 매개 변수를 복잡하지 않게, 문법적으로 융합된 형태로 코드를 작성하기 수월하게 합니다. Dictionary 컬렉션 개체를 생성하고 넘기는 동작을 미화한 것으로 볼 수 있을 것입니다.


 

댓글 남기기