Skip to content

Instantly share code, notes, and snippets.

@jmhdez
Created December 8, 2012 10:46
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jmhdez/4239766 to your computer and use it in GitHub Desktop.
Save jmhdez/4239766 to your computer and use it in GitHub Desktop.
Duck Typing con C#
// 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"));
}
}
}
@kosir35
Copy link

kosir35 commented Jun 28, 2016

What the hell should that be ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment