Implementing InterfaceProxyBehavior back from clay

--HG--
branch : 1.x
This commit is contained in:
Sebastien Ros
2013-04-24 18:02:25 -07:00
parent d038d24f9c
commit 12d90d83e6
2 changed files with 316 additions and 5 deletions

View File

@@ -53,10 +53,24 @@ namespace Orchard.Tests.DisplayManagement {
Assert.That(foo.Bar == null, Is.False);
}
[Test]
public void DuckTyping() {
dynamic foo = new Animal();
foo.Size(42);
ISized sized = foo;
Assert.That(sized.Size, Is.EqualTo(42));
}
}
public class Animal : Composite {
public string Kind { get; set; }
public string Color { get; set; }
}
public interface ISized {
int Size { get; set; }
}
}

View File

@@ -1,11 +1,20 @@
using System.Collections;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Dynamic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Castle.Core.Interceptor;
using Castle.DynamicProxy;
using Microsoft.CSharp.RuntimeBinder;
using Binder = Microsoft.CSharp.RuntimeBinder.Binder;
namespace Orchard.DisplayManagement.Shapes {
public class Composite : DynamicObject {
private readonly IDictionary _props = new HybridDictionary();
public override bool TryGetMember(GetMemberBinder binder, out object result) {
@@ -94,17 +103,305 @@ namespace Orchard.DisplayManagement.Shapes {
return true;
}
public IDictionary Properties {
get { return _props; }
}
public static bool operator ==(Composite a, Nil b) {
return ReferenceEquals(a, b) || null == a;
return null == a;
}
public static bool operator !=(Composite a, Nil b) {
return !(a == b);
}
public IDictionary Properties {
get { return _props; }
protected bool Equals(Composite other) {
return Equals(_props, other._props);
}
public override bool Equals(object obj) {
if (ReferenceEquals(null, obj)) {
return false;
}
if (ReferenceEquals(this, obj)) {
return true;
}
if (obj.GetType() != this.GetType()) {
return false;
}
return Equals((Composite)obj);
}
public override int GetHashCode() {
return (_props != null ? _props.GetHashCode() : 0);
}
#region InterfaceProxyBehavior
private static readonly IProxyBuilder ProxyBuilder = new DefaultProxyBuilder();
static readonly MethodInfo DynamicMetaObjectProviderGetMetaObject = typeof(IDynamicMetaObjectProvider).GetMethod("GetMetaObject");
public override bool TryConvert(ConvertBinder binder, out object result) {
var type = binder.ReturnType;
if (type.IsInterface && type != typeof(IDynamicMetaObjectProvider)) {
var proxyType = ProxyBuilder.CreateInterfaceProxyTypeWithoutTarget(
type,
new[] { typeof(IDynamicMetaObjectProvider) },
ProxyGenerationOptions.Default);
var interceptors = new IInterceptor[] { new Interceptor(this) };
var proxy = Activator.CreateInstance(proxyType, new object[] { interceptors, this });
result = proxy;
return true;
}
result = null;
return false;
}
private class Interceptor : IInterceptor {
public object Self { get; private set; }
public Interceptor(object self) {
Self = self;
}
public void Intercept(IInvocation invocation) {
if (invocation.Method == DynamicMetaObjectProviderGetMetaObject) {
var expression = (Expression)invocation.Arguments.Single();
invocation.ReturnValue = new ForwardingMetaObject(
expression,
BindingRestrictions.Empty,
invocation.Proxy,
(IDynamicMetaObjectProvider)Self,
exprProxy => Expression.Field(exprProxy, "__target"));
return;
}
var invoker = BindInvoker(invocation);
invoker(invocation);
}
private static readonly ConcurrentDictionary<MethodInfo, Action<IInvocation>> Invokers = new ConcurrentDictionary<MethodInfo, Action<IInvocation>>();
private static Action<IInvocation> BindInvoker(IInvocation invocation) {
return Invokers.GetOrAdd(invocation.Method, CompileInvoker);
}
private static Action<IInvocation> CompileInvoker(MethodInfo method) {
var methodParameters = method.GetParameters();
var invocationParameter = Expression.Parameter(typeof(IInvocation), "invocation");
var targetAndArgumentInfos = Pack(
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
methodParameters.Select(
mp => CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.NamedArgument, mp.Name))).ToArray();
var targetAndArguments = Pack<Expression>(
Expression.Property(invocationParameter, invocationParameter.Type, "InvocationTarget"),
methodParameters.Select(
(mp, index) =>
Expression.Convert(
Expression.ArrayIndex(
Expression.Property(invocationParameter, invocationParameter.Type,
"Arguments"),
Expression.Constant(index)), mp.ParameterType))).ToArray();
Expression body = null;
if (method.IsSpecialName) {
if (method.Name.Equals("get_Item")) {
body = Expression.Dynamic(
Binder.GetIndex(
CSharpBinderFlags.InvokeSpecialName,
typeof(object),
targetAndArgumentInfos),
typeof(object),
targetAndArguments);
}
if (body == null && method.Name.Equals("set_Item")) {
var targetAndArgumentInfosWithoutTheNameValue = Pack(
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
methodParameters.Select(
mp => mp.Name == "value" ? CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) : CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.NamedArgument, mp.Name)));
body = Expression.Dynamic(
Binder.SetIndex(
CSharpBinderFlags.InvokeSpecialName,
typeof(object),
targetAndArgumentInfosWithoutTheNameValue),
typeof(object),
targetAndArguments);
}
if (body == null && method.Name.StartsWith("get_")) {
// Build lambda containing the following call site:
// (IInvocation invocation) => {
// invocation.ReturnValue = (object) ((dynamic)invocation.InvocationTarget).{method.Name};
// }
body = Expression.Dynamic(
Binder.GetMember(
CSharpBinderFlags.InvokeSpecialName,
method.Name.Substring("get_".Length),
typeof(object),
targetAndArgumentInfos),
typeof(object),
targetAndArguments);
}
if (body == null && method.Name.StartsWith("set_")) {
body = Expression.Dynamic(
Binder.SetMember(
CSharpBinderFlags.InvokeSpecialName,
method.Name.Substring("set_".Length),
typeof(object),
targetAndArgumentInfos),
typeof(object),
targetAndArguments);
}
}
if (body == null) {
// Build lambda containing the following call site:
// (IInvocation invocation) => {
// invocation.ReturnValue = (object) ((dynamic)invocation.InvocationTarget).{method.Name}(
// {methodParameters[*].Name}: ({methodParameters[*].Type})invocation.Arguments[*],
// ...);
// }
body = Expression.Dynamic(
Binder.InvokeMember(
CSharpBinderFlags.None,
method.Name,
null,
typeof(object),
targetAndArgumentInfos),
typeof(object),
targetAndArguments);
}
if (method.ReturnType != typeof(void)) {
body = Expression.Assign(
Expression.Property(invocationParameter, invocationParameter.Type, "ReturnValue"),
Expression.Convert(body, typeof(object)));
}
var lambda = Expression.Lambda<Action<IInvocation>>(body, invocationParameter);
return lambda.Compile();
}
}
static IEnumerable<T> Pack<T>(T t1) {
if (!Equals(t1, default(T)))
yield return t1;
}
static IEnumerable<T> Pack<T>(T t1, IEnumerable<T> t2) {
if (!Equals(t1, default(T)))
yield return t1;
foreach (var t in t2)
yield return t;
}
static IEnumerable<T> Pack<T>(T t1, IEnumerable<T> t2, T t3) {
if (!Equals(t1, default(T)))
yield return t1;
foreach (var t in t2)
yield return t;
if (!Equals(t3, default(T)))
yield return t3;
}
/// <summary>
/// Based on techniques discussed by Tomáš Matoušek
/// at http://blog.tomasm.net/2009/11/07/forwarding-meta-object/
/// </summary>
public sealed class ForwardingMetaObject : DynamicMetaObject {
private readonly DynamicMetaObject _metaForwardee;
public ForwardingMetaObject(Expression expression, BindingRestrictions restrictions, object forwarder,
IDynamicMetaObjectProvider forwardee, Func<Expression, Expression> forwardeeGetter)
: base(expression, restrictions, forwarder) {
// We'll use forwardee's meta-object to bind dynamic operations.
_metaForwardee = forwardee.GetMetaObject(
forwardeeGetter(
Expression.Convert(expression, forwarder.GetType()) // [1]
)
);
}
// Restricts the target object's type to TForwarder.
// The meta-object we are forwarding to assumes that it gets an instance of TForwarder (see [1]).
// We need to ensure that the assumption holds.
private DynamicMetaObject AddRestrictions(DynamicMetaObject result) {
var restricted = new DynamicMetaObject(
result.Expression,
BindingRestrictions.GetTypeRestriction(Expression, Value.GetType()).Merge(result.Restrictions),
_metaForwardee.Value
);
return restricted;
}
// Forward all dynamic operations or some of them as needed //
public override DynamicMetaObject BindGetMember(GetMemberBinder binder) {
return AddRestrictions(_metaForwardee.BindGetMember(binder));
}
public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) {
return AddRestrictions(_metaForwardee.BindSetMember(binder, value));
}
public override DynamicMetaObject BindDeleteMember(DeleteMemberBinder binder) {
return AddRestrictions(_metaForwardee.BindDeleteMember(binder));
}
public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes) {
return AddRestrictions(_metaForwardee.BindGetIndex(binder, indexes));
}
public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value) {
return AddRestrictions(_metaForwardee.BindSetIndex(binder, indexes, value));
}
public override DynamicMetaObject BindDeleteIndex(DeleteIndexBinder binder, DynamicMetaObject[] indexes) {
return AddRestrictions(_metaForwardee.BindDeleteIndex(binder, indexes));
}
public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) {
return AddRestrictions(_metaForwardee.BindInvokeMember(binder, args));
}
public override DynamicMetaObject BindInvoke(InvokeBinder binder, DynamicMetaObject[] args) {
return AddRestrictions(_metaForwardee.BindInvoke(binder, args));
}
public override DynamicMetaObject BindCreateInstance(CreateInstanceBinder binder, DynamicMetaObject[] args) {
return AddRestrictions(_metaForwardee.BindCreateInstance(binder, args));
}
public override DynamicMetaObject BindUnaryOperation(UnaryOperationBinder binder) {
return AddRestrictions(_metaForwardee.BindUnaryOperation(binder));
}
public override DynamicMetaObject BindBinaryOperation(BinaryOperationBinder binder, DynamicMetaObject arg) {
return AddRestrictions(_metaForwardee.BindBinaryOperation(binder, arg));
}
public override DynamicMetaObject BindConvert(ConvertBinder binder) {
return AddRestrictions(_metaForwardee.BindConvert(binder));
}
}
#endregion
}
public class Nil : DynamicObject {
@@ -178,6 +475,6 @@ namespace Orchard.DisplayManagement.Shapes {
public override string ToString() {
return string.Empty;
}
}
}
}