Piece of Proxy Cake

I’m not 100% decided if I like C#4’s dynamic objects*, but one thing is for sure: creating proxies for types has never been easier.

Proxies are a great way of injecting behavior into existing objects There are powerful and proven utilities for creating proxies in .NET that use Reflection Emit – e.g. the Proven and Honorable Castle DynamicProxy.

C# 4.0 dynamic language features add a new, super-simple way to roll your own proxy implementation: all you need to do is derive from System.Dynamic.DynamicObject, and overload some methods. So here we go:

using System;
using System.Dynamic;
using System.Reflection;

namespace GreenIcicle.DynamicProxy
{
  /// <summary>
  /// Simple implementation of a proxy. 
  /// </summary>
  /// <remarks>
  /// This is not production-quality code, and some things (indexer etc) 
  /// are just missing.
  /// Be nice to the planet: please don't paste into anything you want run 
  /// your nuclear power plant with.
  /// </remarks>
  /// <typeparam name="T">Type of the object wrapped by the proxy.</typeparam>
  public class Proxy<T> : DynamicObject
    where T : class
  {
    /// <summary>
    /// Creates a proxy around an instance of <typeparamref name="T"/>.
    /// </summary>
    /// <param name="wrapped">The object wrapped by the proxy.</param>
    public Proxy( T wrapped )
    {
      if (wrapped == null)
      {
        throw new ArgumentNullException( "wrapped" );
      }
      Wrapped = wrapped;
    }

    /// <summary>
    /// The object wrapped by the proxy.
    /// </summary>
    protected T Wrapped
    {
      get;
      private set;
    }

    /// <summary>
    /// Gets the value of a property on the wrapped object.
    /// </summary>
    public override bool TryGetMember( GetMemberBinder binder, out object result )
    {
      bool propertyExists = false;
      result = null;
      Type type = typeof( T );
      PropertyInfo prop = type.GetProperty( binder.Name );
      if (prop != null)
      {
        result = prop.GetValue( Wrapped, null );
        propertyExists = true;
      }
      return propertyExists;
    }

    /// <summary>
    /// Sets the value of a property on the wrapped object.
    /// </summary>
    public override bool TrySetMember( SetMemberBinder binder, object value )
    {
      bool propertyExists = false;
      Type type = typeof( T );
      PropertyInfo prop = type.GetProperty( binder.Name );
      if (prop != null)
      {
        prop.SetValue( Wrapped, value, null );
        propertyExists = true;
      }
      return propertyExists;
    }

    /// <summary>
    /// Executes a method on the wrapped object.
    /// </summary>
    public override bool TryInvokeMember( InvokeMemberBinder binder, object[] args, out object result )
    {
      bool methodExists = false;
      result = null;
      Type type = typeof( T );
      MethodInfo method = type.GetMethod( binder.Name );
      if (method != null)
      {
        result = method.Invoke( Wrapped, args );
        methodExists = true;
      }
      return methodExists;
    }

    /// <summary>
    /// Converts the instance of the proxy to the type that it wraps.
    /// </summary>
    public override bool TryConvert( ConvertBinder binder, out object result )
    {
      Type type = typeof( T );
      bool canConvert = false;
      result = null;
      if (binder.Type.IsAssignableFrom( type ))
      {
        result = Wrapped;
        canConvert = true;
      }
      return canConvert;
    }
  }
}

The principle is that DynamicObject provides virtual methods to provide the implementation of properties and methods. The MemberBinder object carry the name of the requested property or method. This proxy uses reflection to see if the type member exists, and invokes it.

The thing I like the most is that converting the proxy back into the wrapped type:

RobotDomainObject robotOne = new RobotDomainObject();
dynamic proxy = new Proxy<RobotDomainObject>( robotOne );
RobotDomainObject robotTwo = proxy;

This proxy implementation by itself is not yet every useful. The fun begins where the proxy is extended to provide extra behavior. Here, I want to extend the proxy to support dynamic properties in addition to the ones of the wrapped object.

using System;
using System.Collections.Generic;
using System.Dynamic;

namespace GreenIcicle.DynamicProxy
{
  /// <summary>
  /// Simple implementation of a proxy that supports dynamically added properties
  /// in addition to those of the wrapped object.
  /// </summary>
  /// <remarks>
  /// This is not production-quality code, and some things (indexer etc) 
  /// are just missing.
  /// Be nice to the planet: please don't paste into anything you want run 
  /// your nuclear power plant with.
  /// </remarks>
  /// <typeparam name="T">Type of the object wrapped by the proxy.</typeparam>
  public class Expander<T> : Proxy<T>
    where T : class
  {
    private IDictionary<string, object> m_DynamicProprtyValues;

    /// <summary>
    /// Creates a proxy around an instance of <typeparamref name="T"/>.
    /// </summary>
    /// <param name="wrapped">The object wrapped by the proxy.</param>
    public Expander(T wrapped)
      : base( wrapped )
    {
      // Empty constructor
    }

    /// <summary>
    /// Dictionary of dynamically added property values.
    /// </summary>
    protected IDictionary<string, object> DynamicProprtyValues
    {
      get
      {
        if (m_DynamicProprtyValues == null)
        {
          m_DynamicProprtyValues = new Dictionary<string, object>( StringComparer.Ordinal );
        }
        return m_DynamicProprtyValues;
      }
    }

    /// <summary>
    /// Gets the value of a property on the wrapped object, or, if it does not exist,
    /// retrieves a dynamically added property.
    /// </summary>
    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
      bool propertyExists = base.TryGetMember( binder, out result );
      if (!propertyExists)
      {
        if (DynamicProprtyValues.ContainsKey( binder.Name ))
        {
          result = DynamicProprtyValues[ binder.Name ];
        }
      }
      return true;
    }

    /// <summary>
    /// Sets the value of a property on the wrapped object, or, if it does not exist,
    /// creates a dynamically added property.
    /// </summary>
    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
      bool propertyExists = base.TrySetMember( binder, value );
      if (!propertyExists)
      {
        DynamicProprtyValues[ binder.Name ] = value;
      }
      return true;
    }
  }
}

So now we’ve got a proxy that accepts just any property. What it is good for: MVVM! In a WPF or SIlverlight application, using this kind of extensible proxy can remove the need to create additional objects – and data binding expressions don’t adhere to strong typing anyway.

* Jason Olson said on Hanselminutes that he’d consider dynamic classes in C# mostly as an interoperability feature to fully dynamic languages, and I’d mostly agree to that. If you want to program in a dynamic language, just do so. C# isn’t one. There are some rare legit use cases; methinks proxy generation is one of these.

Advertisements
About

Christian is a software architect/developer. He lives in Germany, reads a lot, and likes cycling.

Tagged with: , , , , ,
Posted in Coding
2 comments on “Piece of Proxy Cake
  1. Francois Nel says:

    If you cast your “dynamic proxy” class to the interface of the “wrapped” class, you actually return a reference to the actual “wrapped” object. This violates the rules of a proxy pattern.

    • Christian says:

      Yes you’re right, it does. It only stays a proxy as long as you do not cast it back – especially because you get the wrapped instance even when you cast to System.Object. This was clearly me getting overexcited with DynamicObject, apologies.

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: