Proxy.cs
using System;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
namespace Cometary
{
///
/// Object that provides a proxy to an internal object.
/// It attempts to limit its use of Reflection to speed up its usage as much as possible.
///
internal sealed clast Proxy
{
#region Utils
internal const BindingFlags ALL = BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.IgnoreCase | BindingFlags.FlattenHierarchy;
private static readonly Func CombineHashes
= ReflectionHelpers.Codeastysisastembly
.GetType("Roslyn.Utilities.Hash")
.GetMethods(ALL)
.First(x => x.Name == "Combine" && x.GetParameters()[0].ParameterType == typeof(int))
.CreateDelegate(typeof(Func)) as Func;
private static int Combine(int a, int b) => CombineHashes(a, b);
internal static MethodBase FindMatchingMethod(MethodBase[] possibleMethods, string name, params object[] args)
{
for (int i = 0; i < possibleMethods.Length; i++)
{
MethodBase mi = possibleMethods[i];
if (mi.Name != name)
continue;
// Same name, but do the parameters match?
ParameterInfo[] parameters = mi.GetParameters();
if (parameters.Length != args.Length)
continue;
for (int j = 0; j < parameters.Length; j++)
{
object arg = args[j];
ParameterInfo parameter = parameters[j];
if (arg == null)
{
if (parameter.ParameterType.GetTypeInfo().IsValueType)
goto Nope;
continue;
}
if (!parameter.ParameterType.IsInstanceOfType(arg))
goto Nope;
}
return mi;
Nope:;
}
return null;
}
#endregion
///
/// Persistent data used across proxies of the same type.
///
private readonly PersistentProxyData data;
///
/// Gets the on which calls will be made.
///
public object Object { get; }
///
/// Gets the type of the object.
///
public Type ObjectType { get; }
///
/// Gets the hash code of the type of the object.
///
public int ObjectTypeHash { get; }
internal Proxy(object obj, Type objType, PersistentProxyData data)
{
Object = obj;
ObjectType = objType;
ObjectTypeHash = obj.GetType().GetHashCode();
this.data = data;
}
public object Invoke(string name, params object[] args) => TryInvoke(name, args, out var result)
? result
: throw new InvalidOperationException();
public T Invoke(string name, params object[] args) => (T)Invoke(name, args);
public object Get(string name) => TryGet(name, out var result) ? result : throw new InvalidOperationException();
public T Get(string name) => (T)Get(name);
public void Set(string name, object value)
{
if (!TrySet(name, value))
throw new InvalidOperationException();
}
///
public bool TryInvoke(string name, object[] args, out object result)
{
object obj = Object;
// Compute key, and try to find an already computed delegate
int key = Combine(Combine(ObjectTypeHash, name.GetHashCode()), args.Length.GetHashCode());
var objType = obj.GetType();
if (data.Invokers.TryGetValue(key, out var del))
{
result = del(obj, args);
return true;
}
// Nothing already computed, compute it now
MethodInfo mi = FindMatchingMethod(objType.GetMethods(ALL), name, args) as MethodInfo;
if (mi == null)
{
result = null;
return false;
}
result = mi.Invoke(obj, args);
return true;
// TODO: Fix this. I can't get it to work.
//data.Invokers[key] = del = Helpers.MakeDelegate(name, il =>
//{
// bool isStatic = mi.IsStatic;
// if (!isStatic)
// {
// Type declaringType = mi.DeclaringType;
// il.Emit(OpCodes.Ldarg_0);
// if (declaringType.GetTypeInfo().IsValueType)
// {
// LocalBuilder loc = il.DeclareLocal(declaringType, false);
// il.Emit(OpCodes.Unbox_Any, declaringType);
// il.Emit(OpCodes.Stloc, loc);
// il.Emit(OpCodes.Ldloca, loc);
// }
// else // Who the f proxies object? if (declaringType != typeof(object))
// {
// il.Emit(OpCodes.Castclast, declaringType);
// }
// }
// for (int j = 0; j < parameters.Length; j++)
// {
// Type type = parameters[j].ParameterType;
// il.Emit(OpCodes.Ldarg_1);
// il.Emit(OpCodes.Ldc_I4, j);
// il.Emit(OpCodes.Ldelem_Ref);
// if (type.GetTypeInfo().IsValueType)
// il.Emit(OpCodes.Unbox_Any, type);
// else if (type != typeof(object))
// il.Emit(OpCodes.Castclast, type);
// }
// il.Emit(isStatic || mi.DeclaringType.GetTypeInfo().IsValueType ? OpCodes.Call : OpCodes.Callvirt, mi);
// if (mi.ReturnType.GetTypeInfo().IsValueType)
// {
// il.Emit(OpCodes.Box, mi.ReturnType);
// }
// else if (mi.ReturnType == typeof(void))
// {
// il.Emit(OpCodes.Ldnull);
// }
// il.Emit(OpCodes.Ret);
//}, mi.DeclaringType);
//result = del(obj, args);
//return true;
}
///
public bool TryGet(string name, out object result)
{
object obj = Object;
// Compute key, and try to find an already computed delegate
int key = Combine(ObjectTypeHash, name.GetHashCode());
if (data.Getters.TryGetValue(key, out var del))
{
result = del(obj);
return true;
}
// Nothing already computed, compute it now
PropertyInfo prop = obj.GetType().GetProperty(name, ALL);
if (prop == null)
{
result = null;
return false;
}
data.Getters[key] = del = Helpers.MakeDelegate(name, il =>
{
if (!(prop.GetMethod ?? prop.SetMethod).IsStatic)
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, prop.GetMethod);
if (prop.PropertyType.GetTypeInfo().IsValueType)
il.Emit(OpCodes.Box, prop.PropertyType);
il.Emit(OpCodes.Ret);
});
result = del(obj);
return true;
}
///
public bool TrySet(string name, object value)
{
// Compute key, and try to find an already computed delegate
int key = Combine(ObjectTypeHash, name.GetHashCode());
if (data.Setters.TryGetValue(key, out var del))
{
del(Object, value);
return true;
}
// Nothing already computed, compute it now
PropertyInfo prop = ObjectType.GetProperty(name, ALL);
if (prop == null)
return false;
data.Setters[key] = Helpers.MakeDelegate(name, il =>
{
if ((prop.GetMethod ?? prop.SetMethod).IsStatic)
{
il.Emit(OpCodes.Ldarg_0);
}
else
{
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
}
if (prop.PropertyType.GetTypeInfo().IsValueType)
il.Emit(OpCodes.Unbox_Any);
else if (prop.PropertyType != typeof(object))
il.Emit(OpCodes.Castclast, prop.PropertyType);
il.Emit(OpCodes.Call, prop.SetMethod);
if (prop.PropertyType.GetTypeInfo().IsValueType)
il.Emit(OpCodes.Box, prop.PropertyType);
il.Emit(OpCodes.Ret);
});
return true;
}
}
}