방프리

24.04.29 Chapter5. 동적 프로그래밍 (Item 45) 본문

C#/More Effective C#

24.04.29 Chapter5. 동적 프로그래밍 (Item 45)

방프리 2024. 4. 29. 23:28

Item 45 : 데이터 주도 동적 타입에는 DynamicObject나 IDynamicMetaObjectProvider를 사용하라

동적 프로그래밍의 큰 장점 하나는 런타임에 public 인터페이스가 바뀌는 타입을 만들 수 있다는 것이다. C#에서는 dynamic과 System.Dynamic.DynamicObject 베이스 클래스 그리고 System.Dynamic.IDynamicMetaObjectProvider 인터페이스를 통해 동적 능력을 갖춘 고유의 타입을 만들 수 있다.

동적 능력을 가지는 타입을 만드는 다음과 같다.

1. System.Dynamic.DynamicObject 상속
- 가장 구현이 쉬운 방법 이 타입은 IDynamicMetaObjectProvider 인터페이스를 구현한 중첩 private 클래스를 가지고 있고,
이 중첩 private 클래스는 표현식을 파싱하고 결과를 DynamicObject 클래스의 가상 메서드에 전달하는 등의 많은 일을 수행한다.

dynamic dynamicProperties = new DynamicPropertyBag();

try
{
    Console.WriteLine(dynamicProperties.Marker);
}
catch (MIcrosoft.CSharp.RuntimeBinder.RuntimeBinderException)
{
    Console.WriteLine("There are no properties");
}

dynamicProperties.Date = DateTime.Now;
dynamicProperties.Name = "Bill Wagner";
dynamicProperties.Title = "Effective C#";
dynamicProperties.Content = "Building a dynamic dictionary";

//DynamicObject의 TrySetMember와 TryGetMember메서드를 재정의한 DynamicPropertyBag
class DynamicPropertyBag : DynamicObject
{
    private Dictionary<string, object> storage = 
        new Dictionary<string, object>();
        
    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        if (storage.ContainsKey(binder.Name))
        {
            result = storage[binder.Name];
            return true;
        }
        result = null;
        return false;
    }
    
    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        string key = binder.Name;
        if (storage.ContainsKey(key))
            storage[key] = value;
        else 
            storage.Add(key, value);
        
        return true;
    }
    
    public override string ToString()
    {
        StringWriter message = new StringWriter();
        foreach (var item in storage)
            message.WritenLine($"{item.Key}:\t{item.Value}");
        return message.ToString();
    }
}

 

2. 다른 베이스 클래스를 상속해야 해서 DynamicObject를 상속할 수 없다면 IDynamicMetaObjectProvider를 구현해서 동적 딕셔너리를 만들면 된다.

class DynamicDictionary2 : IDynamicMetaObjectProvider
{
    DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(
        System.Linq.Expressions.Expression parameter)
    {
        return new DynamicDictionaryMetaObject(parameter, this);
    }
    
    private Dictionary<string, object> storage = new Dictionary<string, object>();
    
    public object SetDictionaryEntry(string key, object value)
    {
        if (storage.ContainsKey(key))
            storage[key] = value;
        else
            storage.Add(key, value);
            
        return value;
    }
    
    public object GetDictionaryEntry(string key)
    {
        object result = null;
        if (storage.ContainsKey(key))
        {
            result = storage[key];
        }
        return result;
    }
    
    public override string ToString()
    {
        StringWriter message = new StringWriter();
        foreach (var item in storage)
            message.WriteLine($"{item.Key}:\t{item.Value}");
        return message.ToString();
    }
    
    public override DynamicMetaObject BindSetMember(
        SetMemberBinder binder,
        DynamicMetaObject value)
    {
        string methodName = "SetDictionaryEntry";
        
        BindingRestrictions restrictions = 
            BindingRestrictions.GetTypeRestriction(Expression, LimitType);
        
        Expression[] args = new Expression[2];
        args[0] = Expression.Constant(binder.Name);
        args[1] = Expression.COnvert(value.Expression, typeof(object));
        
        Expression self = Expression.Convert(Expression, LimitType);
        
        Expression methodCall = Expression.Call(self,
            typeof(DynamicDctionary2).GetMethod(methodName), args);
        
        DynamicMetaObject setDictionaryEntry = 
            new DynamicMetaObject(methodCall, restrictions);
            
        return setDictionaryEntry;
    }
    
    public override DynmaicMetaObject BindGetMember(GetMemberBinder binder)
    {
        string methodName = "GetDictionaryEntry";
        
        Expression[] parameters = new Expression[]
        {
            Expression.Contant(binder.Name)
        };
        
        DynamicMetaObject getDictionaryEntry = 
            new DynamicMetaObject(
                Expression.Call(
                    Expression.Convert(Expression, LimitType),
                    typeof(DynamicDictionary2).GetMethod(methodName), parameters),
                BindingRestrictions.GetTypeRestriction(Expression, LimitType));
                
        return getDictionaryEntry;
    }
}

동적 객체를 호출할 때마다 새로운 DynamicMetaObject를 생성하고 이 객체의 Bind 멤버 중 하나를 호출한다. 따라서 이 메서드들은 효율과 성능을 고려해서 작성해야 한다.

동적 타입을 만들어야 한다면 가장 먼저 System.Dynamic.DynamicObject를, 다른 베이스 클래스를 사용해야 해서 어쩔 수 없다면 IDynamicMetaObjectProvider를 구현하자. 단 구현이 쉽지 않고 동적 타입은 성능을 다소 떨어뜨리며 직접 구현 시 그 정도가 더 심해진다.

Comments