Home > C#, Design Patterns, Tools > Wrap Any Class in Dynamic Goodness

Wrap Any Class in Dynamic Goodness

I use the decorator pattern like it’s going out of style. Not really. I would, though, if it didn’t tend to lead to class explosion. Instead of using it in a broad way, I tend to reach for it in a couple of common scenarios:

  1. when someone else’s object doesn’t do what I want it to, and isn’t open for extension, or
  2. when I want to pass an object to a method that wouldn’t accept the object otherwise.

However, decoration can be tedious, can violate the DRY principle, and can lead to lots of little wrapper classes lying around that just clutter a project up. What if we can prevent that?This is where a generic Wrapper<T> class would come in really handy. With one class, we could wrap not just one class, but any member of set of classes. While this is an interesting idea, we can anticipate some limitations. For one, we would be giving the same new capabilities to all of the wrapped classes. Unless we’re implementing a specific interface, that’s not something we need to do very often. We want to be able to add new members when we want to, and without having to create a new decorator class. The alternatives, like writing a ton of reflection code or emitting bytecode, introduce design-time and run-time overhead that reduce the potential convenience of our generic wrapper class. However, using .NET 4.0’s support for the Dynamic Language Runtime offers the missing piece of our puzzle.

The result: our Dynamic<T> class, presented below. The code shows an example of how you might choose to implement your own generic dynamic wrapper class. It uses reflection in some smart ways to give us the benefit of strong type-iness, while at the same time allowing us to go buck wild adding new properties and methods at runtime with reckless abandon. With just a little extra work, you can even implement ISerializable. Woo wooooo!

So, how does that help us with our two scenarios from before? Well, it can come in handy for Scenario 1 when you want to, say, serialize a class that isn’t marked with [Serializable]. It’s also handy for Scenario 2, where we want wrap an interface implementation around an object that doesn’t already support it. This gives us the ability to pass our original object to other methods that wouldn’t otherwise take them. In the example below, we’re writing a wrapper that allows our original object to comply with the DynamicObject contract. So, with that in mind, let’s get our code on:

// Copyright:     (C) 2011 by Zachary Gramana
// Author:        Zachary Gramana
// Version:       0.1
// Description:   Wraps any class inside of a DynamicObject.
// StartedOn:     7/20/2011
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Reflection;

namespace Utility.Types
{
    /// <summary>
    /// Wraps any object in a DynamicObject, but in a strongly-typed way.
    /// It also logs usage of the object to standard output.
    /// 
    /// Running:
    ///     dynamic item = new Dynamic<String>("Jabberwocky");
    ///     item = new Dynamic<AppDomain>(AppDomain.CurrentDomain);
    ///     item.SetData("Foo", "Bar");
    ///     item.GetData("Foo");
    ///     item.Foo = "Bar";
    ///     var bar = item.Foo;
    /// 
    /// Outputs:
    ///     Dynamic's Inherited Properties:
    ///             Chars
    ///             Length
    /// 
    ///     Dynamic's Inherited Properties:
    ///             DomainManager
    ///             CurrentDomain
    ///             Evidence
    ///             FriendlyName
    ///             BaseDirectory
    ///             RelativeSearchPath
    ///             ShadowCopyFiles
    ///             ActivationContext
    ///             ApplicationIdentity
    ///             ApplicationTrust
    ///             DynamicDirectory
    ///             SetupInformation
    ///             PermissionSet
    ///             IsFullyTrusted
    ///             IsHomogenous
    ///             Id
    ///             MonitoringIsEnabled
    ///             MonitoringTotalProcessorTime
    ///             MonitoringTotalAllocatedMemorySize
    ///             MonitoringSurvivedMemorySize
    ///             MonitoringSurvivedProcessMemorySize
    /// 
    /// Results:
    ///     Invoking instance.SetData() returned '(null)'
    /// 
    /// Results:
    ///     Invoking instance.GetData() returned 'Bar'
    /// 
    /// Results:
    ///     Setting instance.Foo = Bar
    /// 
    /// Results:
    ///     Getting instance.Foo returned 'Bar'
    /// 
    /// </summary>
    public class Dynamic<T> : DynamicObject
    {
        private static readonly Dictionary _propertyCache;
        private        readonly T          _instance;
        private        readonly Dictionary _propertyBag;
        
        /// <summary>
        /// Our type constructor. This populates our lookup store with
        /// all of the property information we need about the object
        /// we are wrapping. This runs just once, the first time
        /// we make an instance of the a particular type.
        /// </summary>
        static Dynamic()
        {
            _propertyCache = typeof(T)
                .GetProperties()
                .ToDictionary(
                    k => k.Name, 
                    k => (Object)k
                   );

            Console.WriteLine("\r\nDynamic's Inherited Properties:", typeof(T).Name);
            foreach (var prop in _propertyCache) 
            {
                Console.WriteLine("\t" + prop.Key);
            }
        }
        
        /// <summary>
        /// Our instance constructor.
        /// </summary>
        /// The object we are wrapping.
        public Dynamic(T instance)
        {
            _instance = instance;
            _propertyBag = new Dictionary(StringComparer.CurrentCulture);
            
            // Log this action.
            Console.WriteLine("\r\nCreated new {0} wrapper object.", this.GetType().Name.TrimEnd(new[] {'`','1'}), this.GetType().GetGenericArguments()[0].Name);
        }
        
        public override bool TryGetMember(System.Dynamic.GetMemberBinder binder, out object result)
        {
            Boolean value;

            Object prop;
            value = _propertyCache.TryGetValue(binder.Name, out prop);

            if (value)
            {
                result = ((PropertyInfo) prop).GetValue(_instance, null);
                value = true;
            }
            else
            {
                value = _propertyBag.TryGetValue(binder.Name, out result);
            }
            
            // Log this action.
            Console.WriteLine("\r\nResults:\r\n\tGetting instance.{0} returned '{1}'", binder.Name, result ?? "(null)");
            
            return value;
        }

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            Object prop;
            var foundProp = _propertyCache.TryGetValue(binder.Name, out prop);
            
            if (foundProp)
            {
                ((PropertyInfo) prop).SetValue(_instance, value, null);
            }
            else
            {
                _propertyBag[binder.Name] = value;
            }
            
            // Log this action.
            Console.WriteLine("\r\nResults:\r\n\tSetting instance.{0} = {1}", binder.Name, value ?? "(null)");

            // You can always add a value to a dictionary,
            // so this method always returns true.
            return true;
        }
        
        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
        {
            // Use reflection to get the method info.
            var method = typeof(T)
                .GetMethod(
                    binder.Name, 
                    args.Select(a => a.GetType())
                        .ToArray()
               );
            
            if (method == null) 
            {
                result = null;
                return false;
            }
            
            result = method.Invoke(_instance, args);

            // Log this action.
            Console.WriteLine("\r\nResults:\r\n\tInvoking instance.{0}() returned '{1}'", method.Name, result ?? "(null)");
            
            // If we didn't throw an exception, we succeeded.
            return true;
        }
        
        public T GetInstance()
        {
            return _instance;
        }
    }
}
Advertisements
  1. 08/12/2011 at 7:52 AM

    Totally dig this! Also, nice use case for a static constructor!

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: