Created
December 8, 2012 10:46
-
-
Save jmhdez/4239766 to your computer and use it in GitHub Desktop.
Duck Typing con C#
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Required libs | |
// - NUnit | |
// - Castle.DynamicProxy | |
using System; | |
using System.Linq; | |
using System.Reflection; | |
using Castle.DynamicProxy; | |
using NUnit.Framework; | |
namespace DynamicMocks | |
{ | |
public static class DuckType | |
{ | |
public static T As<T>(object target) where T : class | |
{ | |
var interceptor = new DuckTypingInterceptor(target); | |
return new ProxyGenerator() | |
.CreateInterfaceProxyWithoutTarget<T>(interceptor); | |
} | |
private class DuckTypingInterceptor : IInterceptor | |
{ | |
private readonly object target; | |
public DuckTypingInterceptor(object target) | |
{ | |
this.target = target; | |
} | |
public void Intercept(IInvocation invocation) | |
{ | |
var methods = target.GetType() | |
.GetMethods(BindingFlags.Instance | BindingFlags.Public); | |
// This should (probably) be cached | |
var method = methods.FirstOrDefault(x => IsCompatible(x, invocation.Method)); | |
if (invocation.GenericArguments != null && | |
invocation.GenericArguments.Length > 0) | |
{ | |
if (!method.IsGenericMethod) | |
throw MissingMethodException(invocation); | |
method = method.MakeGenericMethod(invocation.GenericArguments); | |
} | |
if (method == null) | |
throw MissingMethodException(invocation); | |
invocation.ReturnValue = method.Invoke(target, invocation.Arguments); | |
} | |
private MissingMethodException MissingMethodException(IInvocation invocation) | |
{ | |
return new MissingMethodException( | |
string.Format("Cannot found compatible method {0} on type {1}", | |
invocation.Method.Name, target.GetType().Name)); | |
} | |
private bool IsCompatible(MethodInfo m1, MethodInfo m2) | |
{ | |
// FIXME: handle ref/out paramenters and generic arguments restrictions | |
if (m1.Name != m2.Name || m1.ReturnType != m2.ReturnType) | |
return false; | |
if (!m1.IsGenericMethod) | |
{ | |
var parameterTypes1 = m1.GetParameters().Select(p => p.ParameterType); | |
var parameterTypes2 = m2.GetParameters().Select(p => p.ParameterType); | |
return parameterTypes1.SequenceEqual(parameterTypes2); | |
} | |
return true; | |
} | |
} | |
} | |
[TestFixture] | |
public class DuckInterceptorTest | |
{ | |
public interface ICalculator | |
{ | |
int AddNumbers(int a, int b); | |
string Name { get; set; } | |
string Generic<T>(T t); | |
} | |
public class NotACalculator | |
{ | |
private string name = "I'm not a calculator"; | |
public int AddNumbers(int a, int b) | |
{ | |
return a + b; | |
} | |
public string Name | |
{ | |
get { return name; } | |
set { name = value; } | |
} | |
public string Generic<T>(T t) | |
{ | |
return t.ToString(); | |
} | |
} | |
public interface INamed | |
{ | |
string Name { get; } | |
} | |
[Test] | |
public void DuckTyping_of_methods() | |
{ | |
ICalculator someAsCalculator = DuckType.As<ICalculator>(new NotACalculator()); | |
Assert.That(someAsCalculator.AddNumbers(2, 3), Is.EqualTo(6)); | |
} | |
[Test] | |
public void DuckTyping_of_property_getters_and_setters() | |
{ | |
ICalculator someAsCalculator = DuckType.As<ICalculator>(new NotACalculator()); | |
Assert.That(someAsCalculator.Name, Is.EqualTo("I'm not a calculator")); | |
someAsCalculator.Name = "Yes, you are"; | |
Assert.That(someAsCalculator.Name, Is.EqualTo("Yes, you are")); | |
} | |
[Test] | |
public void DuckTyping_of_generic_methods() | |
{ | |
ICalculator someAsCalculator = DuckType.As<ICalculator>(new NotACalculator()); | |
Assert.That(someAsCalculator.Generic(2), Is.EqualTo("2")); | |
} | |
[Test] | |
public void DuckType_of_anonymous_object_property() | |
{ | |
var anon = new {Name = "Lucas"}; | |
INamed named = DuckType.As<INamed>(anon); | |
Assert.That(named.Name, Is.EqualTo("Lucas")); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
What the hell should that be ?