csharp/71/Cometary/src/Cometary.Core/Internal/Proxying/Proxy.cs

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;
        }
    }
}