DynamicCaster is done.

time to read 37 min | 7285 words

As I said, Lightweight Code Generation presented some really interesting ideas, the most interesting to was this:

IPattern pattern = PatternCaster.Cast<IPattern>(obj);

I was attempting to get that in .Net 1.1, and I think I've some success. Castle.DynamicProxy really made life easy for me in implementing the wrapper. Basically, what is does is to dynamically wrap an instance of an object that implement a method that appears in an interface and let you treat it as if it implemented this interface. Castle.DynamicProxy already does it, with one problem, it doesn't handle missing methods well, you get an unrecoverable ExecutionEngineException when you do that. What I do is simply throw a NotImplementedException, which is more informative and can be recovered from.

Here is how you would use it, it's not as elegant as the Lightweight Code Generation one, but it's still nice:

IPattern pattern = (IPattern)DynamicCaster.Cast(typeof(IPattern),obj);

Here is a real example:

public interface INameable

{

      string Name {get;}

}

 

[TestFixture]

public class DynamicCasterDemo

{

      [Test]

      public void ShowcaseDynamicCaster()

      {

            string xmlString = "<?xml version='1.0'?><tagName/>";

            XmlDocument xdoc = new XmlDocument();

            xdoc.LoadXml(xmlString);

            INameable name = (INameable)DynamicCaster.Cast(typeof(INameable),xdoc.DocumentElement);

            Assert.AreEqual("tagName",name.Name);

      }

}

Here are the tests for DynamicCaster:

[TestFixture]

public class DynamicCasterTests

{

      [Test]

      public void CanCallMethodFromInterfaceOnTypeThatDoesNotImplementIt()

      {

            SimpleObjectWithoutInterface withoutInterface = new SimpleObjectWithoutInterface();

            ISimpleInterface si = (ISimpleInterface)DynamicCaster.Cast(

                  typeof(ISimpleInterface),withoutInterface);

            Assert.AreEqual("Hello, Ayende",si.Greet("Ayende"));

      }

 

      [Test]

      public void ReturnTheSameObjectIfImplementInterface()

      {

            SimpleObject so = new SimpleObject();

            ISimpleInterface si = (ISimpleInterface)DynamicCaster.Cast(typeof(ISimpleInterface),so);

            Assert.AreSame(so,si);

            Assert.AreEqual("Hello, Ayende",si.Greet("Ayende"));

      }

 

      [Test]

      [ExpectedException(typeof(NotImplementedException))]

      public void CallMethodThatIsNotImplementedThrows()

      {

            SimpleObjectWithoutInterface withoutInterface = new SimpleObjectWithoutInterface();

            ISimpleInterface si = (ISimpleInterface)DynamicCaster.Cast(

                  typeof(ISimpleInterface),withoutInterface);

            si.FareWell("Ayende"); 

      }

 

      #region Tests Implementations

 

      public interface ISimpleInterface

      {

            string Greet(string name);

            string FareWell(string name);

      }

 

      public class SimpleObjectWithoutInterface

      {

            public string Greet(string name)

            {

                  return "Hello, "+name;

            }

      }

 

      public class SimpleObject: ISimpleInterface

      {

            public string Greet(string name)

            {

                  return "Hello, "+name;

            }

            public string FareWell(string name)

            {

                  return "Farewell, "+name;

            }

      }

 

      #endregion

}

Finally, here it the code that makes is possible, Castle.DynamicProxy already has 99% of it, btw:

public class DynamicCaster

{

      public static object Cast(Type destinationType, object source)

      {

            if(destinationType==null)

                  throw new ArgumentNullException("destinationType");

            if(source==null)

                  throw new ArgumentNullException("source");

            if(source.GetType().GetInterface(destinationType.Name)!=null)

                  return source;

            ProxyGenerator generator = new ProxyGenerator();

            return generator.CreateProxy(destinationType,new DelegatingInterceptor(source.GetType()),source);

      }

 

      internal class DelegatingInterceptor : StandardInterceptor

      {

            public DelegatingInterceptor(Type type)

            {

                  this.type = type;

                  this.methodCache = new Hashtable();

            }

 

            Type type;

            private Hashtable methodCache;

 

            protected override void PreProceed(IInvocation invocation, params object[] args)

            {

                  if(DoesMethodExists(invocation.Method))

                        throw new NotImplementedException(string.Format("Type '{0}' does not implement method '{1}'!",type.FullName,invocation.Method.ToString()));

            }

 

            private bool DoesMethodExists(MethodInfo method)

            {

                  if(methodCache.Contains(method))

                        return (bool)methodCache[method];

                  ParameterInfo[] parameters = method.GetParameters();

                  Type [] types = new Type[parameters.Length];

                  for (int i = 0; i < parameters.Length; i++)

                  {

                        types[i] = parameters[i].ParameterType;

                  }

                  bool methodExists = type.GetMethod(method.Name,types)==null;

                  methodCache.Add(method,methodExists);

                  return methodExists;

            }

      }

}

If you want to use the pure Castle.DynamicProxy's way, it's as simple as:

public class DynamicCaster

{

      public static object Cast(Type destinationType, object source)

      {

            if(destinationType==null)

                  throw new ArgumentNullException("destinationType");

            if(source==null)

                  throw new ArgumentNullException("source");

            if(source.GetType().GetInterface(destinationType.Name)!=null)

                  return source;

            ProxyGenerator generator = new ProxyGenerator();

            return generator.CreateProxy(destinationType,new StandardInterceptor(),source);

      }

}

[Listening to: Sash! - Encore Une Fois - Sash! - It's my life(03:49)]