mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2026-02-09 09:16:41 +08:00
Implementing InterfaceProxyBehavior back from clay
--HG-- branch : 1.x
This commit is contained in:
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user