using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Castle.DynamicProxy;
using Microsoft.CSharp.RuntimeBinder;
using Binder = Microsoft.CSharp.RuntimeBinder.Binder;
namespace Masuit.Tools.Dynamics.Behaviors;
internal class InterfaceProxyBehavior : ClayBehavior
{
private static readonly IProxyBuilder ProxyBuilder = new DefaultProxyBuilder();
private static readonly MethodInfo DynamicMetaObjectProviderGetMetaObject = typeof(IDynamicMetaObjectProvider).GetMethod("GetMetaObject");
///
public override object ConvertMissing(Func proceed, object self, Type type, bool isExplicit)
{
if (!type.IsInterface || type == typeof(IDynamicMetaObjectProvider))
{
return proceed();
}
var proxyType = ProxyBuilder.CreateInterfaceProxyTypeWithoutTarget(type, new[]
{
typeof(IDynamicMetaObjectProvider)
}, ProxyGenerationOptions.Default);
var interceptors = new IInterceptor[]
{
new Interceptor(self)
};
return Activator.CreateInstance(proxyType, interceptors, self);
}
private class Interceptor(object self) : IInterceptor
{
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);
if (invocation.ReturnValue == null || invocation.Method.ReturnType.IsInstanceOfType(invocation.ReturnValue) || !(invocation.ReturnValue is IClayBehaviorProvider provider))
{
return;
}
var returnValueBehavior = provider.Behavior;
invocation.ReturnValue = returnValueBehavior.Convert(() => returnValueBehavior.ConvertMissing(() => invocation.ReturnValue, invocation.ReturnValue, invocation.Method.ReturnType, false), provider, invocation.Method.ReturnType, false);
}
private static readonly ConcurrentDictionary> Invokers = new();
private static Action BindInvoker(IInvocation invocation)
{
return Invokers.GetOrAdd(invocation.Method, CompileInvoker);
}
private static Action 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.Property(invocationParameter, invocationParameter.Type, "Proxy"), 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)
{
switch (method.Name)
{
case "get_Item":
body = Expression.Dynamic(Binder.GetIndex(CSharpBinderFlags.InvokeSpecialName, typeof(object), targetAndArgumentInfos), typeof(object), targetAndArguments);
break;
case "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);
break;
case { } s when s.StartsWith("get_"):
body = Expression.Dynamic(Binder.GetMember(CSharpBinderFlags.InvokeSpecialName, method.Name.Substring("get_".Length), typeof(object), targetAndArgumentInfos), typeof(object), targetAndArguments);
break;
case { } s when s.StartsWith("set_"):
body = Expression.Dynamic(Binder.SetMember(CSharpBinderFlags.InvokeSpecialName, method.Name.Substring("set_".Length), typeof(object), targetAndArgumentInfos), typeof(object), targetAndArguments);
break;
}
}
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>(body, invocationParameter);
return lambda.Compile();
}
}
private static IEnumerable Pack(T t1, IEnumerable t2)
{
if (!Equals(t1, default(T)))
{
yield return t1;
}
foreach (var t in t2)
{
yield return t;
}
}
///
public sealed class ForwardingMetaObject : DynamicMetaObject
{
private readonly DynamicMetaObject _metaForwardee;
///
public ForwardingMetaObject(Expression expression, BindingRestrictions restrictions, object forwarder, IDynamicMetaObjectProvider forwardee, Func forwardeeGetter) : base(expression, restrictions, forwarder)
{
_metaForwardee = forwardee.GetMetaObject(forwardeeGetter(Expression.Convert(expression, forwarder.GetType())));
}
private DynamicMetaObject AddRestrictions(DynamicMetaObject result)
{
return new DynamicMetaObject(result.Expression, BindingRestrictions.GetTypeRestriction(Expression, Value.GetType()).Merge(result.Restrictions), _metaForwardee.Value);
}
///
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));
}
}
}